SPFA 算法詳解

適用範圍:給定的圖存在負權邊,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的複雜度又過高,SPFA算法便 派上用場了。 我們約定有向加權圖G不存在負權迴路,即最短路徑一定存在。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權迴路,但這不是我們討論的重 點。

算法思想:我們用數組d記錄每個結點的最短路徑估計值,用鄰接表來存儲圖G。我們採取的方法是動態逼近法:設立一個先進先出的隊列用來保存待優化的 結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行鬆弛操作,如果v點的最短路徑估計值有所調整,且v點不在 當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行鬆弛操作,直至隊列空爲止

 

期望的時間複雜度O(ke), 其中k爲所有頂點進隊的平均次數,可以證明k一般小於等於2。

 

實現方法:

  建立一個隊列,初始時隊列裏只有起始點,再建立一個表格記錄起始點到所有點的最短路徑(該表格的初始值要賦爲極大值,該點到他本身的路徑賦爲 0)。然後執行鬆弛操作,用隊列裏有的點作爲起始點去刷新到所有點的最短路,如果刷新成功且被刷新點不在隊列中則把該點加入到隊列最後。重複執行直到隊列 爲空。

判斷有無負環:
  如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖)

 


 

 

 

首先建立起始點a到其餘各點的
最短路徑表格

                                  

首先源點a入隊,當隊列非空時:
 1、隊首元素(a)出隊,對以a爲起始點的所有邊的終點依次進行鬆弛操作(此處有b,c,d三個點),此時路徑表格狀態爲:

                                  

在鬆弛時三個點的最短路徑估值變小了,而這些點隊列中都沒有出現,這些點
需要入隊,此時,隊列中新入隊了三個結點b,c,d

隊首元素b點出隊,對以b爲起始點的所有邊的終點依次進行鬆弛操作(此處只有e點),此時路徑表格狀態爲:

                                 

在最短路徑表中,e的最短路徑估值也變小了,e在隊列中不存在,因此e也要
入隊,此時隊列中的元素爲c,d,e

隊首元素c點出隊,對以c爲起始點的所有邊的終點依次進行鬆弛操作(此處有e,f兩個點),此時路徑表格狀態爲:

                                 

在最短路徑表中,e,f的最短路徑估值變小了,e在隊列中存在,f不存在。因此
e不用入隊了,f要入隊,此時隊列中的元素爲d,e,f

 隊首元素d點出隊,對以d爲起始點的所有邊的終點依次進行鬆弛操作(此處只有g這個點),此時路徑表格狀態爲:

 

 

                               

在最短路徑表中,g的最短路徑估值沒有變小(鬆弛不成功),沒有新結點入隊,隊列中元素爲f,g

隊首元素f點出隊,對以f爲起始點的所有邊的終點依次進行鬆弛操作(此處有d,e,g三個點),此時路徑表格狀態爲:


                               

在最短路徑表中,e,g的最短路徑估值又變小,隊列中無e點,e入隊,隊列中存在g這個點,g不用入隊,此時隊列中元素爲g,e

隊首元素g點出隊,對以g爲起始點的所有邊的終點依次進行鬆弛操作(此處只有b點),此時路徑表格狀態爲:

                           

在最短路徑表中,b的最短路徑估值又變小,隊列中無b點,b入隊,此時隊列中元素爲e,b
隊首元素e點出隊,對以e爲起始點的所有邊的終點依次進行鬆弛操作(此處只有g這個點),此時路徑表格狀態爲:

 

                          

在最短路徑表中,g的最短路徑估值沒變化(鬆弛不成功),此時隊列中元素爲b

隊首元素b點出隊,對以b爲起始點的所有邊的終點依次進行鬆弛操作(此處只有e這個點),此時路徑表格狀態爲:

                         

在最短路徑表中,e的最短路徑估值沒變化(鬆弛不成功),此時隊列爲空了

最終a到g的最短路徑爲14

 

java代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package spfa負權路徑;
 
import java.awt.List;
import java.util.ArrayList;
import java.util.Scanner;
public class SPFA {
    /**
     * @param args
     */
    public long[] result;         //用於得到第s個頂點到其它頂點之間的最短距離
    //數組實現鄰接表存儲
    class edge{
        public int a;//邊的起點
        public int b;//邊的終點
        public int value;//邊的值
        public edge(int a,int b,int value){
            this.a=a;
            this.b=b;
            this.value=value;
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SPFA spafa=new SPFA();
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int s=scan.nextInt();
        int p=scan.nextInt();
        edge[] A=new edge[p];
        for(int i=0;i<p;i++){
            int a=scan.nextInt();
            int b=scan.nextInt();
            int value=scan.nextInt();
            A[i]=spafa.new edge(a,b,value);
        }
        if(spafa.getShortestPaths(n,s,A)){
            for(int i=0;i<spafa.result.length;i++){
                System.out.println(spafa.result[i]+" ");
            }
        }else{
            System.out.println("存在負環");
        }
    }
    /*
     * 參數n:給定圖的頂點個數
     * 參數s:求取第s個頂點到其它所有頂點之間的最短距離
     * 參數edge:給定圖的具體邊
     * 函數功能:如果給定圖不含負權迴路,則可以得到最終結果,如果含有負權迴路,則不能得到最終結果
     */
    private boolean getShortestPaths(int n, int s, edge[] A) {
        // TODO Auto-generated method stub
        ArrayList<Integer> list = new ArrayList<Integer>();
        result=new long[n];
        boolean used[]=new boolean[n];
        int num[]=new int[n];
        for(int i=0;i<n;i++){
            result[i]=Integer.MAX_VALUE;
            used[i]=false;
        }
        result[s]=0;//第s個頂點到自身距離爲0
        used[s]=true;//表示第s個頂點進入數組隊
        num[s]=1;//表示第s個頂點已被遍歷一次
        list.add(s); //第s個頂點入隊
        while(list.size()!=0){
            int a=list.get(0);//獲取數組隊中第一個元素
            list.remove(0);//刪除數組隊中第一個元素
            for(int i=0;i<A.length;i++){
            //當list數組隊的第一個元素等於邊A[i]的起點時
                if(a==A[i].a&&result[A[i].b]>(result[A[i].a]+A[i].value)){
                    result[A[i].b]=result[A[i].a]+A[i].value;
                    if(!used[A[i].b]){
                        list.add(A[i].b);
                        num[A[i].b]++;
                        if(num[A[i].b]>n){
                            return false;
                        }
                        used[A[i].b]=true;//表示邊A[i]的終點b已進入數組隊
                    }
                }
            }
            used[a]=false; //頂點a出數組對
        }
        return true;
    }
}

  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章