複習一下迪傑斯特拉算法,因爲最小生成樹的Prim算法與迪傑斯特拉算法極其相似,再順便複習下最小生成樹,順便找兩道水題驗證代碼正確性。node
該算法用於單源最短路,求一個圖中,從起點S,到終點E的最短路徑c++
算法基於貪心思想,簡單來說就是兩步:算法
依我所見,迪傑斯特拉相似於排序,假設從起點到其餘點的路徑爲邊。優化
牛客網:https://ac.nowcoder.com/acm/problem/17511spa
先遍歷頂點,再遍歷該頂點到其餘頂點的邊,時間複雜度:\(O(n^2)\)。code
#include <bits/stdc++.h> #define ll long long #define MAX 1005 using namespace std; int mp[MAX][MAX],ans[MAX],n,m,s,t; bool used[MAX]; void init(){ scanf("%d%d%d%d",&n,&m,&s,&t); memset(mp,0x3f,sizeof(mp)); memset(ans,0x3f,sizeof(ans)); memset(used,false,sizeof(used)); ans[s] = 0; for(int i = 1;i <= m;i++){ int x,y,v; scanf("%d%d%d",&x,&y,&v); mp[x][y] = mp[y][x] = min(mp[y][x],v); if (x == s) ans[y] = mp[x][y]; else if (y == s) ans[x] = mp[x][y]; } } int dijkstra(int start,int end){ while(true){ int min_edge = 0; for(int i = 1;i <= n;i++)//尋找從起點到其餘點的路線中的最短路線 if (!used[i]&&(!min_edge || ans[i] < ans[min_edge])) min_edge = i; //當找到終點時,可提早退出 if (min_edge == end || !min_edge) break; used[min_edge] = true; for(int i = 1;i <= n;i++) ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]); } return ans[end]==0x3f3f3f3f?-1:ans[end]; } int main(){ init(); printf("%d\n",dijkstra(s,t)); return 0; }
m爲邊數,n爲頂點數
先上結論,時間複雜度\(O(m*logn)\)。排序
for(int i = 1;i <= n;i++)//尋找從起點到其餘點的路線中的最短路線 if (!used[i]&&(!min_edge || ans[i] < ans[min_edge])) min_edge = i;
顯然,對於上面代碼,可使用優先隊列(堆)來使得時間複雜度降爲\(O(logn)\),可是!!!下面還有個for循環,因此自己時間複雜度仍是\(O(n^2)\)。隊列
for(int i = 1;i <= n;i++) ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]);
那麼,能否把這裏也改下呢?顯然,咱們不須要遍歷全部的頂點,由於點到點不必定有邊,這時候咱們只須要遍歷邊便可,也就是把邊存儲起來,即便用鄰接表。get
總結一下,整個過程每一個頂點可能遍歷了屢次,但其中只有一次須要遍歷鄰接的邊,即全部邊也只須要遍歷一次(無向邊就是兩次),因爲使用了堆,因此還須要加上用堆的時間複雜度,因此總的時間複雜度爲\(O(m*logn)\)it
下面是代碼
#include <bits/stdc++.h> #define ll long long #define MAX 1005 using namespace std; int ans[MAX],n,m,s,t;//ans爲起點到某一點的最短路線 vector<pair<int,int>> mp[MAX*10];//鄰接表存儲邊,mp下標表示起點,pair第一個值表示長度,第二個值表示終點 void init(){ scanf("%d%d%d%d",&n,&m,&s,&t); memset(ans,0x3f,sizeof(ans)); ans[s] = 0; for(int i = 1;i <= m;i++){ int x,y,v; scanf("%d%d%d",&x,&y,&v); mp[x].push_back(make_pair(v,y)); mp[y].push_back(make_pair(v,x)); } } int dijkstra(int start,int end){ priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>> > p_que; p_que.push(make_pair(ans[start],start));//pair第一個值表示長度,第二個值表示頂點 while(!p_que.empty()){ pair<int,int> node = p_que.top(); p_que.pop(); if (node.first > ans[node.second]) continue; for(int i = mp[node.second].size()-1;i >= 0;i--){ pair<int,int> temp = mp[node.second][i]; if (ans[temp.second] > ans[node.second]+temp.first){ ans[temp.second] = ans[node.second]+temp.first; p_que.push(make_pair(ans[temp.second],temp.second)); } } } return ans[end]==0x3f3f3f3f?-1:ans[end]; } int main(){ init(); printf("%d\n",dijkstra(s,t)); return 0; }
給定一個無向圖
假設迪傑斯特拉算法是以一個點爲起點,求該起點到其餘全部點的最小值,那麼,最小生成樹的Prim算法則是以一個集合(有多個點)爲起點,求該集合到其餘全部點的最小值,並求和,步驟以下:
牛客網:https://ac.nowcoder.com/acm/problem/15108
顯然,能夠跟迪傑斯特拉同樣,使用堆進行優化,因爲時間問題,只給出普通代碼。
#include <bits/stdc++.h> #define ll long long #define MAX 1005 using namespace std; int mp[MAX][MAX],ans[MAX],c,n,m; bool used[MAX]; int Prim(int start){//幾乎和迪傑斯特拉算法如出一轍 int len = 0; while(true){ int min_edge = 0; for(int i = 1;i <= n;i++)//尋找從集合到其餘點的路線中的最短路線 if (!used[i]&&(!min_edge || ans[i] < ans[min_edge])) min_edge = i; //len >= 0x3f3f3f3f說明沒法生成最小生成樹,即圖不連通 if (!min_edge || len >= 0x3f3f3f3f) break; used[min_edge] = true; len += ans[min_edge];//加上最小值 for(int i = 1;i <= n;i++) ans[i] = min(ans[i],mp[min_edge][i]);//注意這裏和迪傑斯特拉不一樣,也幾乎是惟一的不一樣點 } return len; } void init(int start){ while(~scanf("%d%d%d",&c,&m,&n)){ memset(mp,0x3f,sizeof(mp)); memset(ans,0x3f,sizeof(ans)); memset(used,false,sizeof(used)); ans[start] = 0; for(int i = 1;i <= m;i++){ int x,y,v; scanf("%d%d%d",&x,&y,&v); mp[x][y] = mp[y][x] = min(mp[y][x],v); } printf("%s\n", Prim(1) <= c ?"Yes":"No"); } } int main(){ init(1); return 0; }