1. 程式人生 > >Dijkstra最短路算法詳解

Dijkstra最短路算法詳解

logs alt 成了 解決 img eof cnblogs 這就是 時間

想必大家一定會Floyd了吧,Floyd只要暴力的三個for就可以出來,代碼好背,也好理解,但缺點就是時間復雜度高是O(n3)。

於是今天就給大家帶來一種時間復雜度是O(n2),的算法:Dijkstra(迪傑斯特拉)。

這個算法所求的是單源最短路,好比說你寫好了Dijkstra的函數,那麽只要輸入點a的編號,就可算出圖上每個點到這個點的距離。

我先上一組數據(這是無向圖):

6
2 5
3 8
3 1
4 3
5 7
5 2

圖大概是這個樣子:

技術分享

我們以1為源點,來求所有點到一號點的最短路徑。

先建立一個dis數組,dis[i]表示第i號點到源點(1號點)的估計值,你可能會問為什麽是估計值,因為這個估計值會不斷更新,更新到一定次數就變成答案了,這個我們一會再說。

然後我們在建立一個臨界矩陣,叫做:map,map[i][j]=v表示從i到j這條邊的權值是v。

dis初始值除了源點本身都是無窮大。源點本身都是0.

先從1號點開始。一號點,map[1][2]=5,一號點離2號點是5,比無窮大要小,所以dis[2]從無窮大變成了5。順便,我們用minn記錄距離1號點最短的點,留著以後會用。

dis[0,5,∞,∞,∞]。minn=2。

然後搜到3號點,map[1][3]=8,距離是8,比原來的dis[3]的∞小,於是dis[3]=8。但是8比dis[2]的5要大,所以minn不更新。

dis[0,5,8,∞,∞]

接著分別搜索4,5號點,發現map[1][4],map[1][5]都是∞,所以就不更新。

現在,dis數組所呈現的明顯不是最終答案,因為我們才更新一遍,現在我們開始第二次更新,第二次更新以什麽為開始呢?就是以上一次我們存下來的,minn,相當於把2當源點,求所有點到它的最短路,加上它到真正的源點(1號點)的距離,就是我們要求的最短路。

從2號點開始,搜索3號點,map[2][3]=1,原本dis[3]=8,發現dis[2]+map[2][3]=5+1=6<dis[3](8)所以更新dis[3]為6,minn=3

dis[0,5,6,∞,∞] minn=3.

然後搜索4號點,map[2][4]=3,原本dis[4]=∞,所以,dis[2]+map[2][4]=5+3=8<dis[4](∞)所以更新dis[4]=8,因為map[2][4]=3,3>1,minn不更新。

dis[0,5,6,8,∞] minn=3.

接著搜索5號點,map[2][5]=2,5+2=7,7<∞,dis[5]=7minn不變。

dis[0,5,6,8,7]

二號點搜完,因為minn是3,繼續搜索3號點。

三號點還是按照二號點的方法搜索,發現沒有可以更新的,然後搜索四號。

四號搜5號點,發現8+7>5+2,所以依然不更新,然後跳出循環。

現在的估計值就全部為確定值了:

dis[0,5,6,8,7]

這就是每個點到源點一號點的距離,我們來看一下代碼:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int map[110][110];//這就是map數組,存儲圖
int dis[10010];//dis數組,存儲估計值
int book[10010];//book[i]代表這個點有沒有被當做源點去搜索過,1為有,0為沒有。這樣就不會重復搜索了。
int n,m;
void dijkstra(int u)//主函數,參數是源點編號
{
    memset(dis,88,sizeof(dis));//把dis數組附最大值(88不是十進制的88,其實很大)
    int start=u;//先從源點搜索
    book[start]=1;//標記源點已經搜索過
    for(int i=1;i<=n;i++)
    {
        dis[i]=min(dis[i],map[start][i]);//先更新一遍
    }
    for(int i=1;i<=n-1;i++)
    {
        int minn=9999999;//這就是剛才所說的minn
        for(int j=1;j<=n;j++)
            if(book[j]==0 && minn>dis[j])
            {
                minn=dis[j];
                start=j;//找到離源點最近的點,然後把編號記錄下來,用於搜索。
            }
        book[start]=1;        
        for(int j=1;j<=n;j++)
            dis[j]=min(dis[j],dis[start]+map[start][j]);//以新的點來更新dis。
    }
}
int main()
{
    cin>>n>>m;
    memset(map,88,sizeof(map));
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        map[a][b]=c;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i==j)
                map[i][j]=0;
    dijkstra(1);//以1為源點。
    for(int i=1;i<=n;i++)
        cout<<dis[i]<<" ";
}

這就是用鄰接矩陣實現dijkstra,但是這個算法有一個壞處,就是出現負權邊,這個算法就炸了,要解決負權邊,我以後會給大家帶來Bell man ford

這個算法的復雜度是O(n2),空間復雜度也是n平方,如果用鄰接表來實現,時間復雜度是O(n*m)似乎比n2要大一些,但是空間復雜度會從n平方變成m,少了很多,現在我呈上鄰接表的代碼,如果不會鄰接表的同學可以選擇性忽略,自行百度,我可能會出一期鄰接表。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int value[10010],to[10010],next[10010];
int head[10010],total;
int book[10010];
int dis[10010];
int n,m;
void adl(int a,int b,int c)
{
    total++;
    to[total]=b;
    value[total]=c;
    next[total]=head[a];
    head[a]=total;
}
void dijkstra(int u)
{
    memset(dis,88,sizeof(dis));
    memset(book,0,sizeof(book));
    dis[u]=0;
    for(int i=1;i<=n;i++)
    {
        int start=-1;
        for(int j=1;j<=n;j++)
            if(book[j]==0 && (dis[start]>dis[j] || start==-1))
                start=j;
        book[start]=1;
        for(int e=head[start];e;e=next[e])
            dis[to[e]]=min(dis[to[e]],dis[start]+value[e]);
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        adl(a,b,c);
     } 
     dijkstra(1);
     for(int i=1;i<=n;i++)
         cout<<dis[i]<<" ";
}

Dijkstra最短路算法詳解