Dijkstra 最短路徑算法 秒懂詳解

    想必你們必定會Floyd了吧,Floyd只要暴力的三個for就能夠出來,代碼好背,也好理解,但缺點就是時間複雜度高是O(n³)。ios

   因而今天就給你們帶來一種時間複雜度是O(n²),的算法:Dijkstra(迪傑斯特拉)。git

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

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

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

圖大概是這個樣子:函數

Dijkstra 算法是一種相似於貪心的算法,步驟以下:優化

一、當到一個時間點時,圖上部分的點的最短距離已肯定,部分點的最短距離未肯定。spa

二、選一個全部未肯定點中離源點最近的點,把他認爲成最短距離。3d

三、再把這個點全部出邊遍歷一邊,更新全部的點。code

下面模擬一下:blog

咱們以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不是題解上的minn,這表明的是最近點到源點的距離,start才表明最近的點、
        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(SPFA)

這個算法的複雜度是O(n²),空間複雜度也是n平方,若是用鄰接表來實現,最差狀況,時間複雜度是O(n*m)彷佛比n²要大一些,可是空間複雜度會從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]<<" ";
}

 

一年多了,身爲一個OIer,經歷了太多。

當年那麼畏懼的Dijkstra、鄰接表,如今已是信手拈來。

那個暑假,由於Djkstra名字的朗朗上口,講本身名字改成了Dijkstra,可是逐漸由於SPFA的可處理負權邊,也將Dijkstra,淡忘。

現在忽然想起,加入了堆優化,有人說:一道題若是邊權沒有負數,那麼必定是在卡SPFA。這時候就用到了堆優化的Dijkstra。

一年前提到,樸素的Dijkstra時間複雜度是n^2,被SPFA的m*常數吊打,可是,通過堆優化,Dijkstra的時間複雜度能達到nlogn,若是這個圖特別稠密的話,也就是m特別大(好比徹底圖就是n^2),那麼nlogn是要小於m的,這就用到了Dijkstra

首先堆優化怎麼優化?觀察上面的代碼,每次循環中都再嵌套一個循環求dis值最小的點。這裏,咱們能夠用一個優先隊列,每當搜索到一個新點,扔到優先隊列裏面,這樣每次就取隊首的絕對是最優值。這樣能夠省去for循環。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define in(a) a=read()
#define REP(i,k,n) for(long long i=k;i<=n;i++)
#define MAXN 10010
using namespace std;
typedef pair<long long,long long> P;
inline long long read(){
    long long x=0,t=1,c;
    while(!isdigit(c=getchar())) if(c=='-') t=-1;
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    return x*t;
}
long long n,m,s;
long long total=0,head[MAXN],nxt[MAXN<<10],to[MAXN<<10],val[MAXN<<10];
long long dis[MAXN],vis[MAXN];
priority_queue <P, vector<P>,greater<P> > Q;//優先隊列優化
inline void adl(long long a,long long b,long long c){
    total++;
    to[total]=b;
    val[total]=c;
    nxt[total]=head[a];
    head[a]=total;
    return ;
}
inline void Dijkstra(){
    REP(i,1,n)  dis[i]=2147483647;
    dis[s]=0;
    Q.push(P(0,s));
    while(!Q.empty()){
        long long u=Q.top().second;//取出dis最小的點
        Q.pop();//彈出
        if(vis[u])  continue;
        vis[u]=1;
        for(long long e=head[u];e;e=nxt[e])
            if(dis[to[e]]>dis[u]+val[e]){
                dis[to[e]]=dis[u]+val[e];
                Q.push(P(dis[to[e]],to[e]));//插入
            }
    }
    return ;
}
int main(){
    in(n),in(m),in(s);
    long long a,b,c;
    REP(i,1,m)  in(a),in(b),in(c),adl(a,b,c);
    Dijkstra();
    REP(i,1,n)  printf("%lld ",dis[i]);
}
    
相關文章
相關標籤/搜索