[笔记]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 }
View Code

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 }
View Code

附视频中的图例

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
View Sample

比较

我们可以发现,Prim算法每次都要遍历一遍所有点,找出与已确定区间相连的边,无论两点之间有无边,我们都需要每轮遍历一次。所以Prim算法的时间复杂度是$O(n^2)$,适用于点少边多的图(稠密图),存储方式也一般使用邻接矩阵;

而Kruskal算法与点无关,整个过程就是按边的权值排序,依次选择,所以与点的数量无关。故时间复杂度是$O(mlogm)$,适用于点多边少的图(稀疏图)。存图只需要把边记录下来即可,不过需要用并查集维护一下点。

posted @ 2023-11-17 16:26  Sinktank  阅读(184)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.