【最小生成树】Prim算法和Kruskal算法

一、Prim

1、描述

  MST(Minimum Spanning Tree,最小生成树)问题有两种通用的解法,Prim算法就是其中之一,它是从点的方面考虑构建一颗MST,大致思想是:设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V,再从集合U-V中找到另一点b使得点b到V中任意一点的权值最小,此时将b点也加入集合V;以此类推,现在的集合V={a,b},再从集合U-V中找到另一点c使得点c到V中任意一点的权值最小,此时将c点加入集合V,直至所有顶点全部被加入V,此时就构建出了一颗MST。因为有N个顶点,所以该MST就有N-1条边,每一次向集合V中加入一个点,就意味着找到一条MST的边。

 

2、说明:

初始状态:

 【最小生成树】Prim算法和Kruskal算法_第1张图片

 

设置2个数据结构:

lowcost[i]:表示以i为终点的边的最小权值,当lowcost[i]=0说明以i为终点的边的最小权值=0,也就是表示i点加入了MST

mst[i]:表示对应lowcost[i]的起点,即说明边是MST的一条边,当mst[i]=0表示起点i加入MST

 

 

我们假设V1是起始点,进行初始化(*代表无限大,即无通路):

 

lowcost[2]=6,lowcost[3]=1,lowcost[4]=5,lowcost[5]=*,lowcost[6]=*

mst[2]=1,mst[3]=1,mst[4]=1,mst[5]=1,mst[6]=1,(所有点默认起点是V1)

 

明显看出,以V3为终点的边的权值最小=1,所以边=1加入MST

 【最小生成树】Prim算法和Kruskal算法_第2张图片

 

此时,因为点V3的加入,需要更新lowcost数组和mst数组:

lowcost[2]=5,lowcost[3]=0,lowcost[4]=5,lowcost[5]=6,lowcost[6]=4

mst[2]=3,mst[3]=0,mst[4]=1,mst[5]=3,mst[6]=3


明显看出,以V6为终点的边的权值最小=4,所以边=4加入MST

 【最小生成树】Prim算法和Kruskal算法_第3张图片

 


此时,因为点V6的加入,需要更新lowcost数组和mst数组:

lowcost[2]=5,lowcost[3]=0,lowcost[4]=2,lowcost[5]=6,lowcost[6]=0

mst[2]=3,mst[3]=0,mst[4]=6,mst[5]=3,mst[6]=0


明显看出,以V4为终点的边的权值最小=2,所以边=4加入MST

 

【最小生成树】Prim算法和Kruskal算法_第4张图片

 


此时,因为点V4的加入,需要更新lowcost数组和mst数组:

lowcost[2]=5,lowcost[3]=0,lowcost[4]=0,lowcost[5]=6,lowcost[6]=0

mst[2]=3,mst[3]=0,mst[4]=0,mst[5]=3,mst[6]=0


明显看出,以V2为终点的边的权值最小=5,所以边=5加入MST

 【最小生成树】Prim算法和Kruskal算法_第5张图片

 


此时,因为点V2的加入,需要更新lowcost数组和mst数组:

lowcost[2]=0,lowcost[3]=0,lowcost[4]=0,lowcost[5]=3,lowcost[6]=0

mst[2]=0,mst[3]=0,mst[4]=0,mst[5]=2,mst[6]=0


很明显,以V5为终点的边的权值最小=3,所以边=3加入MST


lowcost[2]=0,lowcost[3]=0,lowcost[4]=0,lowcost[5]=0,lowcost[6]=0

mst[2]=0,mst[3]=0,mst[4]=0,mst[5]=0,mst[6]=0


至此,MST构建成功,如图所示:

 【最小生成树】Prim算法和Kruskal算法_第6张图片

以上转载自https://blog.csdn.net/yeruby/article/details/38615045

接下来来看看代码吧。

 1 #include
 2 using namespace std;
 3 int n,m,ans;
 4 int pre[5001];//与这个节点连接的父节点 
 5 int sum[5001];//这个点到它的父节点的值 
 6 bool flag[5001];//判断有没有加入最小树 
 7 struct node{
 8     int v,w;//连接的节点与边权 
 9     node(){};
10     node(int vv,int ww)
11     {
12         v=vv;
13         w=ww;
14     }
15 };
16 vector mp[10001];//方便存图 
17 int prim()
18 {
19     int last=1;//记录 
20     pre[1]=0;//代表这是这棵树的父节点 
21     flag[1]=true;//加入最小数 
22     for(int i=1;i//要循环n-1次 
23     {
24         for(int j=0;j//更新每次的已连接节点 
25         {
26             int vv=mp[last][j].v;
27             int ww=mp[last][j].w;
28             if(ww//如果更短 
29             {
30                 pre[vv]=last;//更新父节点 
31                 sum[vv]=ww;//更新值 
32             }
33         }
34         int min=0x7fffffff;//记录最短边 
35         int minid=0;//记录这个最短边连接的点 
36         for(int j=1;j<=n;j++)
37         {
38             if(sum[j]false)//寻找没进最小树且更小 
39             {
40                 min=sum[j];//替换 
41                 minid=j;
42             }
43         }
44         ans+=min;//加边权 
45         flag[minid]=true;//加入最小树 
46         last=minid;//记录这个点 
47     }
48     return ans;//返回最小生成树的所耗总值 
49 }
50 int main()
51 {
52     cin>>n>>m;
53     for(int i=1;i<=n;i++)
54     {
55         sum[i]=0x7fffffff; //初始化 
56     }
57     for(int i=1;i<=m;i++)
58     {
59         int x,y,z;
60         cin>>x>>y>>z;
61         mp[x].push_back(node(y,z));
62         mp[y].push_back(node(x,z));//记录边 
63     }
64     cout<<prim();
65     return 0;
66 }

二、Kruskal算法

以下转载自https://blog.csdn.net/weixin_44336954/article/details/91345907

1、描述

  一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。 。
​  在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得

的 w(T) 最小,则此 T 为 G 的最小生成树。

  最小生成树其实是最小权重生成树的简称。

  许多应用问题都是一个求无向连通图的最小生成树问题。例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同;另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

  这一章主要介绍Kruskal算法。

  Kruskal算法的时间复杂度:O(m*log(n))(点数n边数m)

【最小生成树】Prim算法和Kruskal算法_第7张图片

 

2、主要思路

  输入之后对边权值进行排序,然后按边权值从小到大进行合并(merge)操作,如果操作成功(被合并的两个点不在一棵树上),则把这两个顶点的边权值加入总数,最后输出total即可。

3、主要使用:

  “并查集。”
首先把get和merge函数写好,为了方便,我把merge写成了bool类型:如果成功合并(要求合并的两个顶点不在一棵树上)就返回true。
然后是最正常的运用结构体进行循环读入,读入完成之后写cmp排序函数按边权值从小到大进行排序。
接下来才和并查集扯上关系,所以要重新定义fa数组,然后进行初始化;

接下来来看看代码吧。

 1 #include
 2 using namespace std;
 3 int n,m,ans;
 4 int fa[10001];
 5 struct node{
 6     int v1,v2,w;//两个点和边权 
 7     node(){};
 8     node(int vv1,int vv2,int ww)
 9     {
10         v1=vv1;
11         v2=vv2;
12         w=ww;
13     }
14 }mp[200001]; 
15 bool sj(node x,node y)//比较函数 
16 {
17     return x.w<y.w ;
18 }
19 int get(int x)//找祖先 
20 {
21     if(fa[x]==x)return x;
22     else return fa[x]=get(fa[x]);
23 }
24 int Kruskal()
25 {
26     int num=0; 
27     for(int i=1;i<=m;i++)
28     {
29         int x=mp[i].v1;
30         int y=mp[i].v2;
31         if(get(x)!=get(y))//如果这两个点不在同一集合,我们就把他们合在一起(加入最小树) 
32         {
33             ans+=mp[i].w;//加上这个边权 
34             fa[get(x)]=get(y);//合并 
35             num++;//边数加加 
36             if(num==n-1)break;//最小生成树边为点-1 
37         }
38     }
39     return ans;
40 }
41 int main()
42 {
43     cin>>n>>m;
44     for(int i=1;i<=n;i++)
45     {
46         fa[i]=i;//每个祖先是自己 
47     }
48     for(int i=1;i<=m;i++)
49     {
50         int x,y,z;
51         cin>>x>>y>>z;
52         mp[i].v1=x;
53         mp[i].v2=y;
54         mp[i].w=z;//存图 
55     }
56     sort(mp+1,mp+1+m,sj);//排序 
57     cout<<Kruskal();
58     return 0;
59 }

 

你可能感兴趣的:(【最小生成树】Prim算法和Kruskal算法)