复习一下迪杰斯特拉算法,因为最小生成树的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; }