最小生成树及模板题
一、最小生成树简介
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
通俗的说就是有n个结点的带权无向图,用n-1条边连通,即为生成树,其中权值最小的生成树就是最小生成树。这篇文章介绍的是kruskal算法。
二、模板题目
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 N,MN,MN,M,表示该图共有 NNN 个结点和 MMM 条无向边。
接下来 MMM 行每行包含三个整数 Xi,Yi,ZiX_i,Y_i,Z_iXi,Yi,Zi,表示有一条长度为 ZiZ_iZi 的无向边连接结点 Xi,YiX_i,Y_iXi,Yi。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例
输入
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出
7
样例解释:
所以最小生成树的总边权为 2+2+3=7。
解题过程:
kruskal算法是先以边的权值对边进行排序,然后先选取权值较小的边,依次连接,并利用并查集来判断是否为环,若形成了环则跳过此边,直到已经使用的边的数量为总结点数减一。
先用结构体存边,存点:
struct Node {
int rnk, pre;
void init(int x) {
rnk = 0, pre = x;
}
} T[MAX_N];
struct edge {
int head, tail, value;
} E[MAX_M];
用并查集判断是否为环(Same):
int Root(int x) {
if (T[x].pre != x) T[x].pre = Root(T[x].pre);
return T[x].pre;
}
bool Same(int x, int y) {
return Root(x) == Root(y);
}
全部代码:
#include<bits/stdc++.h>
#define MAX_N 5005
#define MAX_M 200005
using namespace std;
struct DSU {
struct Node {
int rnk, pre;
void init(int x) {
rnk = 0, pre = x;
}
} T[MAX_N];
void Build(int n) {
for (int i = 1; i <= n; i++)
T[i].init(i);
}
int Root(int x) {
if (T[x].pre != x)
T[x].pre = Root(T[x].pre);
return T[x].pre;
}
void Unite(int x, int y) {
x = Root(x), y = Root(y);
if (x == y) return;
if (T[x].rnk > T[y].rnk) T[y].pre = x;
else {
T[x].pre = y;
if (T[x].rnk == T[y].rnk)
T[y].rnk++;
}
}
bool Same(int x, int y) {
return Root(x) == Root(y);
}
} dsu;
struct edge {
int head, tail, value;
} E[MAX_M];
bool cmp(edge x, edge y) {
return x.value < y.value;
}
int kruskal(int n, int m) {
dsu.Build(n);
sort(E, E + m, cmp);
int ans = 0, js = 1;
for (int i = 0; i < m && js < n; i++) {
if (!dsu.Same(E[i].head, E[i].tail)) {
ans += E[i].value, js++;
dsu.Unite(E[i].head, E[i].tail);
}
}
if (js < n) return -1;
else return ans;
}
int main(){
int n,m,xi,yi,zi;
cin >> n >> m;
for(int i=1;i<=m;i++){
cin >> E[i].head >> E[i].tail >> E[i].value;
}
cout << kruskal(n,m);
}