SPFA算法學習筆記


一.理論准備

        為了學習網絡流,先水一道spfa。

        SPFA算法是1994年西南交通大學段凡丁提出,只要最短路徑存在,SPFA算法必定能求出最小值,SPFA對Bellman-Ford算法優化的關鍵之處在於意識到:只有那些在前一遍松弛中改變了距離估計值的點,才可能引起他們的鄰接點的距離估計值的改變。為什么隊列為空就不改變了呢?就是因為要到下一點必須經過它的前一個鄰接點。。SPFA可以處理負權邊。很多時候,給定的圖存在負權邊,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的復雜度又過高,SPFA算法便派上用場了。簡潔起見,我們約定有向加權圖G不存在負權回路,即最短路徑一定存在。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權回路。

        初始化: dis數組全部賦值為Inf(無窮大,不能是map[s][i]),path數組全部賦值為s(即源點),或者賦值為-1,表示還沒有知道前驅,然后dis[s]=0;  表示源點不用求最短路徑,或者說最短路就是0。將源點入隊;另外記住在整個算法中有頂點入隊了要記得標記vis數組,有頂點出隊了記得消除那個標記(可能多次入隊)。

        核心:讀取隊頭頂點u,並將隊頭頂點u出隊(記得消除標記);將與點u相連的所有點v進行松弛操作,如果能更新估計值(即令d[v]變小),那么就更新,另外,如果點v沒有在隊列中,那么要將點v入隊(記得標記),如果已經在隊列中了,那么就不用入隊以此循環,直到隊空為止就完成了單源最短路的求解。

        判斷有無負環:如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖),假設這個節點的入度是k(無向權則就是這個節點的連接的邊)如果進入這個隊列超過k,說明必然有某個邊重復了,即成環;換一種思路:用DFS,假設存在負環a1->a2->…->an->a1。那么當從a1深搜下去時又遇到了a1,那么直接可以判斷負環了所有用。當某個節點n次進入隊列,則存在負環,此時時間復雜度為O(n*m),n為節點,m為邊。

        SPFA算法有兩個優化算法 SLF 和 LLL: SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾。 LLL:Large Label Last 策略,設隊首元素為i,隊列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出對進行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。 在實際的應用中SPFA的算法時間效率不是很穩定,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法。個人覺得LLL優化每次要求平均值,不太好,為了簡單,我們可以之間用c++STL里面的優先隊列來進行SLF優化。


二.算法實現

        直接去把HDU1874AC了吧。

  1: import java.util.Comparator;
  2: import java.util.PriorityQueue;
  3: import java.util.Queue;
  4: import java.util.Scanner;
  5: /*
  6:  * 原來一直wa,重寫了一遍,AC了
  7:  * SLF優化
  8:  */
  9: public class HD1874 {
 10: 
 11:   static int n,m;
 12:   static int[][] map  = new int[205][205];
 13:   static int[] dis  = new int[205];
 14:   static boolean[] vis  = new boolean[205];
 15:   /*
 16:    * 路徑最大值是10000,不能設置成10005就行,還要考慮和
 17:    * 也不能是整形最大值,否則一加就溢出了
 18:    */
 19:   static final int Inf = 0x3f3f3f3f;
 20:   
 21:   public static void main(String[] args) {
 22:     Scanner sc = new Scanner(System.in);
 23:     // 記錄前驅點 。若path[i]=j,表示從s到i的最短路徑中i的前一個點是j
 24:     //int[] path;
 25:     int u,v,w;
 26:     while(sc.hasNext()) {
 27:       n = sc.nextInt();
 28:       m = sc.nextInt();
 29:       for(int i=0; i<205; i++) {
 30:         for(int j=i; j<205; j++) {
 31:           map[i][j] = Inf;
 32:           map[j][i] = Inf;
 33:         }
 34:       }
 35:       for(int i=0; i<m; i++) {
 36:         u = sc.nextInt();
 37:         v = sc.nextInt();
 38:         w = sc.nextInt();
 39:         //多重邊
 40:         if(map[u][v]>w) {
 41:           map[v][u] = w;
 42:           map[u][v] = w;
 43:         }
 44:       }
 45:       int s = sc.nextInt();
 46:       int t = sc.nextInt();
 47:       spfa(s);
 48:       //題目上有st<n,所以不必判斷dis[t]是否越界
 49:       //起點終點相同的話答案是0
 50:       if(Inf==dis[t]) {
 51:         System.out.println(-1);
 52:       }else {
 53:         System.out.println(dis[t]);
 54:       }
 55:     }
 56:   }
 57: 
 58:   private static void spfa(int s) {
 59:     
 60:     for(int i=0; i<205; i++) {
 61:       vis[i] = false;
 62:       //初始化為map[s][i]第一組數據就錯了
 63:       dis[i] = Inf;
 64:     }
 65:     dis[s] = 0;
 66:     vis[s] = true;
 67:     Comparator<Integer> cmp = new Comparator<Integer>() {
 68:           
 69:           public int compare(Integer o1, Integer o2) {
 70:             int i = (int)o1;
 71:             int j = (int)02;
 72:             if(dis[i]>dis[j]) {
 73:               return 1;
 74:             }else if(dis[i]==dis[j]){
 75:               return 0;
 76:             }else {
 77:               return -1;
 78:             }
 79:           }
 80:     };
 81:     //面向接口編程;205代表優先隊列(是類)的容量
 82:     Queue<Integer> q = new PriorityQueue<Integer>(205, cmp);
 83:     q.clear();
 84:     q.offer(s);
 85:     while(!q.isEmpty()) {
 86:       int head = q.poll();
 87:       //該注意的是有些點可能重復入隊,所以出隊的點也要重新置未標記 
 88:       vis[head] = false;
 89:       for(int i=0; i<n; i++) {
 90:         //dis[head]不可能是INF,map[head][i]可能是INF
 91:         int temp = dis[head] + map[head][i];
 92:         if(temp<dis[i]) {
 93:           //path[i] = head
 94:           dis[i] = temp;
 95:           if(!vis[i]) {
 96:             //用一個數組在此記錄入隊次數,大於n就存在負環;如何事先判斷
 97:             q.offer(i);
 98:             vis[i] = true;
 99:           }
100:         }
101:       }
102:     }
103:   }
104:     
105: }
106: 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM