赞
踩
目录
最小生成树(Minimum Spanning Tree,简称MST,在一个连通的无向图,最小生成树是指包含图中所有顶点的一棵树,且该树的所有边的权重之和最小,关键在于树和最小两个关键。
在上面的示例图中我们知道了生成的数是不能有闭环的,那么我们怎么去理解最小的意思。
其实在每个点之间相连接的边中是有边的长度的,如下:
我们需要找出的边构成生成树,并且包含图中的所有顶点,使得边的权重之和最小。
那么我们很好奇,我们为什么要去寻找这么一个最小生成树呢?
设想,面对一个交通落后的城市
在当今经济发展迅速的阶段,国家肯定不允许城市之间没有一条公路将各个城市串通起来,但是也不会盲目乱铺设,因为人力和成本是很多的,那么串通每个城市之间的公路就是边,我们利用最小生成树将这些城市串通起来的同时,也减少了成本的浪费,这个就是最小生成树的应用之一。
最小生成树在现实应用中具有广泛的用途。一主要应用领域是网络设计,例如通信网络和计算机网络的规划。通过选择最小生成树,可以确保网络中的节点连接最优,减少通信成本。在城市规划中,最小生成树被用于设计交通网络,确保道路布局经济高效。电力系统规划也借助最小生成树,以建立最优的电力输送网络。此外,在电路板设计、社交网络分析以及物流规划等领域,最小生成树都能提供有效的解决方案,降低资源消耗,提高系统效率。这些应用反映了最小生成树作为一种优化工具在解决各种连接和布线问题上的重要性。
找出最小生成树有这么两个方法。
Kruskal算法和Prim算法。
虽然是两个方法,但是都有使用了贪心的思想。
下面将对两个方法进行详介。
我们以这个图为例:
1.先将边按照从小到大分好
2.初始一个空的最小生成树
3.慢慢从小到大填入边
当我们连接4--6时,此时闭成一个环,用红色代表这条边不加入。
此时加入的边为顶点数(7)-1,所以代表已经全部串通,所以最小生成树的边之和就是:
1+2+2+3+4+4=16
首先可以建立一个结构体,里面放上边联通的两个节点和边的长度。
- struct node{
- int x,y,w;
- }a[1000];
因为Kruskal算法需要使用到并查集,所以下面是必不可少的Find函数。
- int Find(int x)
- {
- if(pre[x]==x) return x;
- return pre[x]=Find(pre[x]);
- }
由于需要排序,我们这里直接使用c++的sort函数,加上自定义函数cmp
- bool cmp(node &x,node &y)
- {
- return x.w<y.w;
- }
这样就构成了我们的核心函数Kruskal函数。
- void Kruskal()
- {
- sort(a+1,a+1+m,cmp);
- for(int i=1;i<=m;i++){
- int fx=Find(a[i].x);
- int fy=Find(a[i].y);
- if(fx==fy) continue;
- pre[fx]=fy;
- ans+=a[i].w;
- cnt++;
- if(cnt==n-1)
- break;
- }
- }
得到完整代码:
- #include<bits/stdc++.h>
- using namespace std;
- struct node{
- int x,y,w;
- }a[1000];
- int n,m,ans=0,cnt=0;
- int pre[1000];
- int Find(int x)
- {
- if(pre[x]==x) return x;
- return pre[x]=Find(pre[x]);
- }
- bool cmp(node &x,node &y)
- {
- return x.w<y.w;
- }
- void Kruskal()
- {
- sort(a+1,a+1+m,cmp);
- for(int i=1;i<=m;i++){
- int fx=Find(a[i].x);
- int fy=Find(a[i].y);
- if(fx==fy) continue;//如果在一个集合就跳过
- pre[fx]=fy;
- ans+=a[i].w;
- cnt++;
- if(cnt==n-1)//当加入的边等于顶点数-1,就停止循环
- break;
- }
- }
- int main()
- {
- cin>>n>>m;
- for(int i=1;i<=n;i++){
- pre[i]=i;//初始化
- }
- for(int i=1;i<=m;i++){
- cin>>a[i].x>>a[i].y>>a[i].w;
- }
- Kruskal();//进入核心代码
- if(cnt==n-1)
- cout<<ans<<endl;//可以得到答案,直接输出
- else
- cout<<-1<<endl;//不可以接通
- return 0;
- }

我们输出来试试看,是不是得到答案16。
还是以这个图为例子
以0这个节点出发,我们有四条边可以加入。
我们选择这最小的。
然后从0和2这两个点出发,继续寻找,发现有两个2,我们选其中一个就行。
按照这种规则下去,我们得到了最小生成树,红色边是由于构成了闭环而没有使用的边,,绿色为使用的边。
我们来计算一下使用到的所有边的长度:
1+2+2+3+4+4=16。
我们发现这个就是我们前得到的答案。
首先生成最小堆。
- struct node
- {
- int u, w; // u是节点,w是花费
- bool operator < (node x) const
- {
- return w > x.w;
- } //重载运算符,生成最小堆
- };
将每个顶点初始化为无穷大
- for(int i = 0; i < n; i++)
- dis[i] = INF; // 初始化dis[]
加边函数
- void add(int u, int v, int w)
- {
- a[++num].to = v;
- a[num].w = w;
- a[num].next = head[u];
- head[u] = num;
- } // 加边
得到完整代码:
- #include<bits/stdc++.h>
- using namespace std;
- const int INF = 1e9;
- struct data
- {
- int to, next, w;
- } a[400002]; //链式前向星
- struct node
- {
- int u, w; // u是节点,w是花费
- bool operator < (node x) const
- {
- return w > x.w;
- } //重载运算符,生成最小堆
- };
- int dis[200010], head[200010], num;// dis是最小花费,head存边,num为边数
- bool vis[200010];
- int n, m, ans = 0, cnt = 0;
- void add(int u, int v, int w)
- {
- a[++num].to = v;
- a[num].w = w;
- a[num].next = head[u];
- head[u] = num;
- } // 加边
- priority_queue<node> q;
- void Prim()
- {
- q.push((node) {0, 0});
- while(!q.empty()||cnt < n) // 进行n-1次
- {
- node x = q.top();
- q.pop();
- if(vis[x.u]) continue;
- vis[x.u] = true;
- cnt++;
- ans += x.w;
- for(int i = head[x.u]; i ; i = a[i].next)
- if(a[i].w < dis[a[i].to]) {
- dis[a[i].to] = a[i].w;
- q.push((node) {a[i].to, a[i].w});
- }
- }
- }
- int main()
- {
- cin>>n>>m;
- for(int i = 1; i <= m; i++)
- {
- int u, v, w;
- cin>>u>>v>>w;
- add(u, v, w); add(v, u, w); // 需要加两次无向图
- }
- for(int i = 0; i < n; i++)
- dis[i] = INF; // 初始化dis[]
- Prim();
- if(cnt==n)
- cout<<ans<<endl;
- else
- cout<<"-1"<<endl;
- return 0;
- }

我们输出一下试试。
代码得到正确答案。
里面的E为边的数量,V为顶点的数量。
是不是已经完美掌握了。
赶紧去练练题目,来巩固一下知识。
P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1194 买礼物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
最小生成树就介绍到这里。
本篇完~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。