[算法学习记录] 最小生成树
最小生成树是一个无向连通图中的一棵包含图中所有顶点的树,且具有最小总权重,在实际场景中常用于网络设计、电力传输、通信网络等领域。
最小生成树有两种常见实现算法,分别是Prim算法与Kruskal算法,它们的核心思想都是贪心。
Prim算法
Prim算法的实现方法类似于Dijkstra算法,都包含一个朴素写法与一个最优写法,不同的是,Prim算法每操作一个节点都要更新所有点到intree的距离d,具体看代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e5+5, inf = 2e18;
ll n, m, d[N], sum;
bitset<N>intree;
//标记节点是否在树上
struct Edge
{
ll x,w;
bool operator < (const Edge&u)const
{
if(w != u.w)return w > u.w;
}
};
vector<Edge> g[N];
//构建邻接表,并重载小于号实现小根堆
void Prim(int st)
{
for(int i = 1;i<=n;i++)d[i] = inf;
//初始化
priority_queue<Edge> pq;
pq.push({st,d[st] = 0});
while(pq.size())
{
int x = pq.top().x;pq.pop();
if(intree[x]) continue;
//如果这个点在树上,就跳过它
intree[x] = true;
//把将要操作的点标记为在树上
sum += d[x];
//更新总权值
d[x] = 0;
//在树上的点标记为0
for(const auto &t : g[x])
{
int u = t.x,w = t.w;
if(d[x] + w < d[u])
{
pq.push({u, d[u] = d[x] + w});
}
//更新其子节点到父节点的距离,并把子节点放入堆中
}
}
//松弛
}
//prim算法实现最小生成树
void solve()
{
cin >> n >> m;
while(m--)
{
int u, v, w;cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
//无向图,u->v,v->u,所以添加两次
}
Prim(1);
for(int i = 1;i<=n;i++)if(!intree[i]) sum = -1;
//检查是否所有点都在这棵最小生成树上,如果有一个点不在,说明构不成最小生成树。
cout << sum << '\n';
//输出边权之和
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//取消同步流,节省时间
int _ = 1;
while(_--)solve();
return 0;
}
Kruskal算法
Kruskal也是一种最小生成树算法,与Prim算法不同的是,Kruskal算法是通过不断选择权值最小的边直到生成一个最小生成树。
Kruskal算法的核心是贪心与并查集,利用贪心的思想每次选择权值最小的边,利用并查集来管理边之间的联通。
Kruskal算法多用于稀疏图
实现方法:
- 将所有的边在按权值大小升序排列;
- 创建一个并查集来管理各点的连通性;
- 选择已排序的边,如果该边与最小生成树不联通,则将它们合并,并重复该步骤,直到处理完所有的边。
代码实现:
星码Starrycoding P72 【模板】最小生成树
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
ll pre[N], ans, n, m;
struct Edge
{
int u, v, w;
bool operator < (const Edge&m)const
{
return w < m.w;
}
//根据权值、节点本身的位置以及其子节点的位置这个顺序升序排序(贪心)
};
vector<Edge> es;
int root(int x)
{
return pre[x] = (pre[x]== x ? x : root(pre[x]));
}
//寻找一个节点的根节点并进行路径压缩以优化时间复杂度
void solve()
{
cin >> n >> m;
while(m--)
{
int u, v, w;cin >> u >> v >> w;
es.push_back({u, v, w});
}
sort(begin(es),end(es));
//结构体中重载了小于号,排序规则被重定义根据权重w>u>v的顺序排序
for(int i = 1;i <= n;i++) pre[i] = i;
//并查集初始化 ,每个点默认根节点为该节点本身
for(const auto &t : es)
{
int u = t.u,v = t.v,w = t.w;
if(root(u) == root(v))continue;
//两者的根节点相同,说明该点已经在最小生成树上了 ,直接跳过
ans += w;
//如果不在树上,加上它的权值
pre[root(u)] = root(v);
//并把该节点连接在树上
}
for(int i = 1;i < n;i++)if(root(i)!=root(i + 1)) ans = -1;
//如果有一个点的根节点不相同,说明无法构成最小生成树
cout << ans <<"\n";
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _ = 1;
while(_--)solve();
return 0;
}
本人初学算法,技艺不精,如有纰漏,敬请指正。