[笔记]Kruskal和Prim算法的比较
公式如果有滚动条影响阅读的话,右键公式>Math Settings>Math Renderer>SVG即可。
最小生成树及两个算法的简介
生成树即在一张图上找到连接所有顶点的一棵树,显然如果点数为$n$,这棵生成树的边数就是$n-1$。
最小生成树(Minimum Spanning Tree)就是这张图上边权之和最小的生成树。
常用的两个最小生成树算法有两个:Kruskal和Prim。两个算法都是基于贪心的思想,理解起来也十分简单,下面把边权具象为边的长度来理解。
Kruskal算法的思想是:将所有边长度从小到大排序,依次遍历,如果加入这条边不会形成环,就直接加入。当边数$=$点数$-1$时,完成最小生成树。
(该算法因需要判断成环,所以需要并查集的参与,如果不会可以去搜索更多信息)
Prim算法的思想是:随便选一个点开始,向周围找一个距离最近的点,形成一个整体,接着找这个整体与周围距离最近的点,加入这个整体,重复上述步骤直到所有点被加入。
推荐参考此视频,下面的代码与视频内容较为贴合,结合理解更好(特别是Prim算法的一堆数组)。
代码
Kruskal
1 #include<iostream> 2 #include<queue> 3 #include<algorithm> 4 #define MAXN 1010 5 #define MAXM 1010 6 using namespace std; 7 struct edge{ 8 int u,v,w; 9 }edges[MAXM]; 10 bool cmp(edge a,edge b){ 11 return a.w<b.w; 12 } 13 int n,m,fa[MAXN],sum; 14 int find(int x){ 15 if(fa[x]==x) return x; 16 return fa[x]=find(fa[x]); 17 } 18 queue<int> path; 19 int main(){ 20 cin>>n>>m; 21 for(int i=0;i<m;i++){ 22 cin>>edges[i].u>>edges[i].v>>edges[i].w; 23 } 24 for(int i=0;i<n;i++) fa[i]=i; 25 sort(edges,edges+m,cmp); 26 int cnt=0; 27 for(int i=0;i<m;i++){ 28 int u=find(edges[i].u),v=find(edges[i].v); 29 if(u==v) continue; 30 fa[u]=v; 31 cnt++; 32 sum+=edges[i].w; 33 path.push(i); 34 if(cnt>=n-1) break; 35 } 36 cout<<sum<<"\n"; 37 //记录边来存图,所以此处只输出边的编号 38 while(!path.empty()){ 39 cout<<path.front()<<" "; 40 path.pop(); 41 } 42 return 0; 43 }
Prim
1 #include<iostream> 2 #include<cstring> 3 #define MAXN 1010 4 using namespace std; 5 int map[MAXN][MAXN],mindist[MAXN],parent[MAXN]; 6 bool selected[MAXN]; 7 int n,m,sum; 8 int main(){ 9 memset(mindist,127,sizeof mindist);//INF 10 int maxx=mindist[0]; 11 cin>>n>>m; 12 for(int i=0;i<m;i++){ 13 int u,v,w; 14 cin>>u>>v>>w; 15 map[u][v]=map[v][u]=w; 16 } 17 int current=0; 18 for(int i=0;i<n-1;i++){ 19 selected[current]=1;//1.Update 20 for(int j=0;j<n;j++){ 21 if(!selected[j]&&map[current][j]){ 22 if(map[current][j]<mindist[j]){ 23 mindist[j]=map[current][j],parent[j]=current; 24 } 25 } 26 } 27 int minn=maxx,minpos=-1;//2.Scan 28 for(int j=0;j<n;j++){ 29 if(!selected[j]&&mindist[j]<minn){ 30 minn=mindist[j],minpos=j; 31 } 32 } 33 sum+=minn,current=minpos;//3.Add 34 } 35 cout<<sum<<endl; 36 //用了邻接矩阵存,所以此处只输出节点编号 37 for(int i=1;i<n;i++){ 38 cout<<i<<" => "<<parent[i]<<"\n"; 39 } 40 return 0; 41 }
附视频中的图例
Input: 9 14 0 1 4 0 7 8 1 7 11 1 2 8 7 8 7 6 7 1 2 8 2 6 8 6 2 5 4 5 6 2 2 3 7 3 5 14 3 4 9 4 5 10 Output: 37
比较
我们可以发现,Prim算法每次都要遍历一遍所有点,找出与已确定区间相连的边,无论两点之间有无边,我们都需要每轮遍历一次。所以Prim算法的时间复杂度是$O(n^2)$,适用于点少边多的图(稠密图),存储方式也一般使用邻接矩阵;
而Kruskal算法与点无关,整个过程就是按边的权值排序,依次选择,所以与点的数量无关。故时间复杂度是$O(mlogm)$,适用于点多边少的图(稀疏图)。存图只需要把边记录下来即可,不过需要用并查集维护一下点。