深入理解dijkstra+堆優化
其實就這幾種代碼幾種結構,記住了完全就可以舉一反三,所以多記多練多優化多思考。
Dijkstra
最短路滿足
最優子結構性質,所以是一個
動態規划問題。最短路的最優子結構可以描述為:
D(s, t) = {Vs ... Vi ... Vj ... Vt}表示s到t的最短路,
其中i和j是這條路徑上的兩個中間結點,
那么D(i, j)必定是i到j的最短路,這個性質是顯然的,可以用反證法證明。
基於上面的最優子結構性質,如果存在這樣一條最短路D(s, t) = {Vs ... Vi Vt},其中i和t是最短路上相鄰的點,那么D(s, i) = {Vs ... Vi} 必定是s到i的最短路。Dijkstra算法就是基於這樣一個性質,通過最短路徑長度遞增,逐漸生成最短路。
Dijkstra算法是最經典的最短路算法,用於計算正權圖的單源最短路(
Single Source Shortest Path,源點給定,通過該算法可以求出起點到所有點的最短路),它是基於這樣一個事實:
如果源點到x點的最短路已經求出,並且保存在d[x] ( 可以將它理解為D(s, x) )上,那么可以利用x去更新 x能夠直接到達的點 的最短路。即:
d[y] = min{ d[y], d[x] + w(x, y) } y為x能夠直接到達的點,w(x, y) 則表示x->y這條有向邊的邊權
具體算法描述如下:對於圖G = <V, E>,源點為s,d[i]表示s到i的最短路,visit[i]表示d[i]是否已經確定(布爾值)。
1) 初始化 所有頂點 d[i] = INF, visit[i] = false,令d[s] = 0;
2) 從所有visit[i]為false的頂點中找到一個d[i]值最小的,令x = i; 如果找不到,算法結束;
3) 標記visit[x] = true, 更新和x直接相鄰的所有頂點y的最短路: d[y] = min{ d[y], d[x] + w(x, y) }
(第三步中如果y和x並不是直接相鄰,則令w(x, y) = INF)
實例:
輸入:
5 7
1 2 2
2 5 2
1 3 4
1 4 7
3 4 1
2 3 1
3 5 6
1 #include <bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 struct node{ 5 int to; 6 int w; 7 }; 8 int edgeNum[100]; 9 vector<node> vec[100]; 10 int dis[100]; 11 bool vis[100]; 12 13 void addEdge(int a,int b,int w){ 14 edgeNum[a]++; 15 node *p=new node(); 16 p->to=b; 17 p->w=w; 18 vec[a].push_back(*p); 19 } 20 21 void init(){ 22 cin>>n>>m; 23 for(int i=1;i<=m;i++){ 24 int a,b,w; 25 cin>>a>>b>>w; 26 addEdge(a,b,w); 27 addEdge(b,a,w); 28 } 29 } 30 31 void dijkstra(int start){ 32 memset(dis,0x3f,sizeof(dis)); 33 dis[start]=0; 34 for(int i=0;i<edgeNum[start];i++) { 35 int b=vec[start][i].to; 36 int w=vec[start][i].w; 37 dis[b]=w; 38 } 39 vis[start]=1; 40 for(int k=1;k<=n-1;k++){ 41 int minV=0x7fffffff,min_i; 42 for(int i=1;i<=n;i++){ 43 if(!vis[i]&&dis[i]<minV){ 44 minV=dis[i]; 45 min_i=i; 46 } 47 } 48 vis[min_i]=true; 49 for(int i=0;i<edgeNum[min_i];i++){ 50 int b=vec[min_i][i].to; 51 int w=vec[min_i][i].w; 52 if(!vis[b]&&dis[b]>dis[min_i]+w){ 53 dis[b]=dis[min_i]+w; 54 } 55 } 56 57 } 58 59 60 61 } 62 63 void print(){ 64 for(int i=1;i<=n;i++) 65 cout<<dis[i]<<" "; 66 cout<<endl; 67 } 68 69 int main(){ 70 freopen("in.txt","r",stdin); 71 init(); 72 dijkstra(2); 73 print(); 74 return 0; 75 }
上面的代碼說幾點:
1、13行到19行的代碼可以通過給結構體添加構造函數來優化。
2、dijkstra中的節點如果改成u,v的話更清晰
3、朴素的dijkstra分為如下幾步:初始化dis數組,n-1輪(找最優節點,更新)
求節點1到其它節點的距離:

堆優化:
1 #include <bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 struct node{ 5 int to; 6 int w; 7 }; 8 int edgeNum[100]; 9 vector<node> vec[100]; 10 int dis[100]; 11 bool vis[100]; 12 13 void addEdge(int a,int b,int w){ 14 edgeNum[a]++; 15 node *p=new node(); 16 p->to=b; 17 p->w=w; 18 vec[a].push_back(*p); 19 } 20 21 void init(){ 22 cin>>n>>m; 23 for(int i=1;i<=m;i++){ 24 int a,b,w; 25 cin>>a>>b>>w; 26 addEdge(a,b,w); 27 addEdge(b,a,w); 28 } 29 } 30 31 void dijkstra(int start){ 32 memset(dis,0x3f,sizeof(dis)); 33 dis[start]=0; 34 for(int i=0;i<edgeNum[start];i++) { 35 int b=vec[start][i].to; 36 int w=vec[start][i].w; 37 dis[b]=w; 38 } 39 vis[start]=1; 40 for(int k=1;k<=n-1;k++){ 41 int minV=0x7fffffff,min_i; 42 for(int i=1;i<=n;i++){ 43 if(!vis[i]&&dis[i]<minV){ 44 minV=dis[i]; 45 min_i=i; 46 } 47 } 48 vis[min_i]=true; 49 for(int i=0;i<edgeNum[min_i];i++){ 50 int b=vec[min_i][i].to; 51 int w=vec[min_i][i].w; 52 if(!vis[b]&&dis[b]>dis[min_i]+w){ 53 dis[b]=dis[min_i]+w; 54 } 55 } 56 57 } 58 } 59 60 61 //dijkstra的堆優化 62 struct qnode{ 63 int i_i; 64 int dis_i; 65 qnode(int i,int dis_i){ 66 this->i_i=i; 67 this->dis_i=dis_i; 68 } 69 }; 70 struct myCmp{ 71 bool operator ()(const qnode &p1,const qnode &p2){ 72 return p1.dis_i>p2.dis_i; 73 } 74 }; 75 priority_queue<qnode,vector<qnode>,myCmp> q; 76 void dijkstra_2(int start){ 77 memset(dis,0x3f,sizeof(dis));//和SPFA一樣,這里最開始全都是無窮大 78 dis[start]=0; 79 q.push(qnode(start,dis[start])); 80 while(!q.empty()){ 81 qnode p=q.top(); 82 q.pop(); 83 int min_i= p.i_i; 84 int minV=p.dis_i; 85 if(vis[min_i]) continue; 86 vis[min_i]=true; 87 for(int i=0;i<edgeNum[min_i];i++){ 88 int b=vec[min_i][i].to; 89 int w=vec[min_i][i].w; 90 if(!vis[b]&&dis[b]>dis[min_i]+w){ 91 dis[b]=dis[min_i]+w; 92 q.push(qnode(b,dis[b])); 93 } 94 } 95 96 } 97 } 98 99 void print(){ 100 for(int i=1;i<=n;i++) 101 cout<<dis[i]<<" "; 102 cout<<endl; 103 } 104 105 int main(){ 106 freopen("in.txt","r",stdin); 107 init(); 108 //dijkstra(2); 109 dijkstra_2(1); 110 print(); 111 return 0; 112 }
關於上面的代碼說幾點:
1、堆優化的話priority_queue得非常熟悉
2、這里堆優化的時候使用了結構體,里面的成員是i和dis[i],其實這個dis[i]可以直接寫在外面,但是比較規則還是得自己定義
3、用隊列做的所有的題核心代碼一定是while(!queue.empty()){}
4、還是要多寫多練,要熟悉,基礎打好
5、和SPFA一樣,如果點更新成功就加進隊列
求節點1到其它節點的距離:

堆優化2
1 #include <bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 struct node{ 5 int to; 6 int w; 7 }; 8 int edgeNum[100]; 9 vector<node> vec[100]; 10 int dis[100]; 11 bool vis[100]; 12 13 void addEdge(int a,int b,int w){ 14 edgeNum[a]++; 15 node *p=new node(); 16 p->to=b; 17 p->w=w; 18 vec[a].push_back(*p); 19 } 20 21 void init(){ 22 cin>>n>>m; 23 for(int i=1;i<=m;i++){ 24 int a,b,w; 25 cin>>a>>b>>w; 26 addEdge(a,b,w); 27 addEdge(b,a,w); 28 } 29 } 30 31 void dijkstra(int start){ 32 memset(dis,0x3f,sizeof(dis)); 33 dis[start]=0; 34 for(int i=0;i<edgeNum[start];i++) { 35 int b=vec[start][i].to; 36 int w=vec[start][i].w; 37 dis[b]=w; 38 } 39 vis[start]=1; 40 for(int k=1;k<=n-1;k++){ 41 int minV=0x7fffffff,min_i; 42 for(int i=1;i<=n;i++){ 43 if(!vis[i]&&dis[i]<minV){ 44 minV=dis[i]; 45 min_i=i; 46 } 47 } 48 vis[min_i]=true; 49 for(int i=0;i<edgeNum[min_i];i++){ 50 int b=vec[min_i][i].to; 51 int w=vec[min_i][i].w; 52 if(!vis[b]&&dis[b]>dis[min_i]+w){ 53 dis[b]=dis[min_i]+w; 54 } 55 } 56 57 } 58 } 59 60 61 //dijkstra的堆優化 62 struct myCmp{ 63 bool operator ()(int a,int b){ 64 return dis[a]>dis[b]; 65 } 66 }; 67 priority_queue<int,vector<int>,myCmp> q; 68 void dijkstra_2(int start){ 69 memset(dis,0x3f,sizeof(dis));//和SPFA一樣,這里最開始全都是無窮大 70 dis[start]=0; 71 q.push(start); 72 while(!q.empty()){ 73 int u=q.top(); 74 q.pop(); 75 if(vis[u]) continue; 76 vis[u]=true; 77 for(int i=0;i<edgeNum[u];i++){ 78 int b=vec[u][i].to; 79 int w=vec[u][i].w; 80 if(!vis[b]&&dis[b]>dis[u]+w){ 81 dis[b]=dis[u]+w; 82 q.push(b); 83 } 84 } 85 86 } 87 } 88 89 void print(){ 90 for(int i=1;i<=n;i++) 91 cout<<dis[i]<<" "; 92 cout<<endl; 93 } 94 95 int main(){ 96 freopen("in.txt","r",stdin); 97 init(); 98 //dijkstra(2); 99 dijkstra_2(1); 100 print(); 101 return 0; 102 }
關於上面的代碼說幾點:
1、dijkstra的優先隊列優化寫法和spfa非常像,只不過spfa多加了一個是否在隊列里面的標志
2、這里儲存圖用的是vector數組
3、優先隊列里面的元素是int,然而我們還是重寫了優先隊列的比較函數,因為隊列里面是節點編號,但是我們要比較的是dis[i]
4、和SPFA一樣,dis[i]最開始全都是無窮大 ,並且最開始只更新dis[start]=0
求節點1到其它節點的距離:

其它代碼:
#include<iostream> #include<cstdio> #include<queue> using namespace std; int n,m,S,tot,Next[500010],head[20000],tree[500010],val[500010]; bool visit[20000]; long long dis[20000]; struct cmp { bool operator()(int a,int b) { return dis[a]>dis[b]; } }; priority_queue<int,vector<int>,cmp> Q; void add(int x,int y,int z) { tot++; Next[tot]=head[x]; head[x]=tot; tree[tot]=y; val[tot]=z; } int main() { scanf("%d%d%d",&n,&m,&S); tot=0; for (int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); if (x==y) continue; add(x,y,z); } for (int i=1;i<=n;i++) { visit[i]=false; dis[i]=2147483647; } Q.push(S); dis[S]=0; while (!Q.empty()) { int u=Q.top(); Q.pop(); if (visit[u]) continue; visit[u]=true; for (int i=head[u];i;i=Next[i]) { int v=tree[i]; if (!visit[v]&&dis[v]>dis[u]+(long long)val[i]) { dis[v]=dis[u]+val[i]; Q.push(v); } } } for (int i=1;i<=n-1;i++) printf("%lld ",dis[i]); printf("%lld\n",dis[n]); return 0; }
vector數組版
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int v; 5 int w; 6 node(int v,int w){ 7 this->v=v; 8 this->w=w; 9 } 10 }; 11 vector<node> vec[100]; 12 int edgeNum[100]; 13 int n,m; 14 int dis[100]; 15 bool vis[100]; 16 17 void addEdge(int u,int v,int w){ 18 edgeNum[u]++; 19 vec[u].push_back(node(v,w)); 20 } 21 22 void init(){ 23 cin>>n>>m; 24 for(int i=1;i<=m;i++){ 25 int u,v,w; 26 cin>>u>>v>>w; 27 addEdge(u,v,w); 28 addEdge(v,u,w); 29 } 30 } 31 32 struct myCmp{ 33 bool operator ()(const int &u,const int &v){ 34 return dis[u]>dis[v]; 35 } 36 }; 37 priority_queue<int,vector<int>,myCmp> q; 38 void dijkstra(int start){ 39 memset(dis,0x3f,sizeof(dis)); 40 q.push(start); 41 dis[start]=0; 42 while(!q.empty()){ 43 int u=q.top(); 44 q.pop(); 45 if(vis[u]) continue; 46 vis[u]=true; 47 for(int i=0;i<edgeNum[u];i++){ 48 int v=vec[u][i].v; 49 int w=vec[u][i].w; 50 if(!vis[v]&&dis[v]>dis[u]+w){ 51 dis[v]=dis[u]+w; 52 q.push(v); 53 } 54 } 55 } 56 } 57 58 void print(){ 59 for(int i=1;i<=n;i++){ 60 cout<<dis[i]<<" "; 61 } 62 cout<<endl; 63 } 64 65 int main(){ 66 freopen("in.txt","r",stdin); 67 init(); 68 dijkstra(2); 69 print(); 70 return 0; 71 }
