迪傑斯特拉算法詳解


簡述

  迪傑斯特拉算法是一種基於貪心法求有向圖或無向圖單源最短路的算法,其本質就是把頂點集划分為兩部分,已求出最短路徑的集合S和未求出最短路徑的集合U,U集里面每個點都有一個邊權,代表源點通過S集里的點到達U集的那個點的最短路徑(注意這里的最短不一定是全局最短),S一開始只有源點,U里面和源點的邊權為路徑本身,不相鄰的邊權為inf,通過貪心不斷地把U集合里面的頂點加入S,直到求完源點到所有頂點的最短路徑。暴力時間復雜度為O(n方),經過堆優化可為O(nlogn)。

思想過程

  如何將集合U里的點一個一個加入集合S呢?

  我們發現,我們可以確定,U里面頂點的最小邊權就是源點到該路徑的最短路徑。

  例如該圖:

  

   A和B直接和源點相鄰,那么U集中最小邊權對應的頂點就是源點到該頂點的最短路徑,在此圖就是源點到B點的最短路徑為2,因為A點的邊權5不是最小邊權,所以在這一步還不能確定是不是最短路徑。那為什么U集里的最小邊權就是源點到該點的最短路徑呢?我們用反證法,假設最小邊權不是源點到該點設為y的最小路徑,那么必然存在一個點設為x,使得源點到x的距離加x到y的距離小於U里的最小邊權,那么源點到x的距離就要小於這個最小邊權,矛盾!所以結論得證!

  在加入S集后,該點要對其他點的邊權進行松弛,什么意思呢?U集合里面的邊權是源點到該頂點的路徑距離嘛,一開始只有和源點相鄰的邊,但隨着S集合新點的加入,源點到其他點的距離會被改變,因為S集合有新的點x加入,那么源點到其他的點的距離可能會被更小的源點到x的距離加x到其他點的距離取代,即如上圖:B加入S之后,U集里面A的邊權就可以更新為 源點-B+B-A,為4,4比5小,故更新。所以每當有點加入S集的時候,都要對U里面的其他點的邊權進行松弛。

  於是不斷地加入,松弛,加入,松弛,直到全部點都在S里面,就能跑出源點S到其余所有點的最短路徑了。

算法模擬

  因為S集和U集沒有相同的部分,所以我們用一個集合加個標記就可以區分兩個集合。

  

  以此圖為例,vis[i]為1代表i點加入了S集,設起點為D,對應表如下:

  •剛開始S集只有D點,初始化D點自己到自己的最短路為0,與D點直接相鄰的只有C和E  

  

  •現在將dis里最小的邊權加到S里->D到C的最短距離為3,同時松弛其他點

  

  •因為C的加入使B和F點可達,松弛后D到B和F的距離為13和9,然后選擇邊權最小的E加入S集

  

  •E的加入松弛了F點和G點,同理現在將F加入S集

  

  •F的加入松弛了A點,現在最小邊權的點是G,將G加入S集

  

  •G的加入並未能松弛其他點,此時將B加入S集

  

  •B點的加入也並未能松弛其余點,最后將A點加入S集

  

   經過上述操作,我們把所有點都加進S集合里了,最后得出的dis[i]就是源點D到i點的最短距離!

算法的正確性證明

  U集邊權的本質就是源點通過干S集里面的點到達目標點的最短路徑長度,證明迪傑斯特拉算法就是要證明U集合里的最小邊權就是全局最短路徑,所有的操作都是基於這個結論的,最小邊權是最短路徑,加入S集,因為S集更新了所以U集的邊權肯會變,然后重復。

  那么這個結論如何證明呢?

  設源點為u,現有一點v屬於U集且該點權值最小,設為x,我們使用反證法,假設x不是全局最短路徑,那必然存在另一點k不屬於S集,使得u到k+k到v小於u到v。那么u到k的權值必然小於u到v,那么u到v的權值就不是U集里面最小的了,應該是k才對,矛盾,命題得證!

代碼詳解

   代碼實現分為三部,找最短邊權點及其權值,將它加入S集,松弛其他點,在找最小邊權的地方復雜度最大,所以總復雜度為O(n方)

int m[maxn][maxn],d[maxn],vis[maxn];
void dij(int u,int n){//起點為u,總頂點數為n 
    d[u]=0;//自己到自己就是0 
    for(int i=1;i<=n;i++){
        int tmp=inf,k=1;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&d[j]<tmp){
                k=j;
                tmp=d[j];
            }
        }//此時tmp就是U集中最小的邊權,k就是對應的頂點 
        
        vis[k]=1;//k點加入S集 
        
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&tmp+m[k][j]<d[j]){//因為k的加入,可以松弛U的其他點 
                d[j]=tmp+m[k][j];
            }
        }
    }
}

模板

int m[maxn][maxn],d[maxn],vis[maxn];
void dij(int u,int n){
    d[u]=0;
    for(int i=1;i<=n;i++){
        int tmp=inf,k=1;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&d[j]<tmp){
                k=j;
                tmp=d[j];
            }
        } 
        vis[k]=1;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&tmp+m[k][j]<d[j]){
                d[j]=tmp+m[k][j];
            }
        }
    }
}
暴力n方

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 


免責聲明!

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



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