最小生成树两连
Posted hermitgreen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最小生成树两连相关的知识,希望对你有一定的参考价值。
最小生成树两连
并查集优化的克鲁斯卡尔算法和优先队列+链式前向星优化的普利姆算法
Kruskal
Kruskal是常用的最小生成树算法,算法利用贪心思想,每次选择没用过且不构成环的边的最小边,直到选择了n-1条边,通常我们用并查集这个数据结构去优化,优化后的Kruskal算法复杂度是(O(mlogm+malpha m)),复杂度只和边的数量有关,所以适用于稀疏图
#include <bits/stdc++.h>
using namespace std;
int n; // n个点
int m; // m条边
int ans; //最小生成树的权值
const int MAXM = 2e5 + 5; //边的数据范围
const int MAXN = 5005; //点的数据范围
struct node {
int u;
int v;
int w;
} e[MAXM];
bool cmp(node a, node b) { return a.w < b.w; }
int parent[MAXN];
int rnk[MAXN];
void init() {
ans = 0;
for (int i = 0; i <= n; i++) {
rnk[i] = 0;
parent[i] = i;
}
}
int find(int x) {
if (parent[x] == x) {
return x;
} else {
return parent[x] = find(parent[x]); //路径压缩
}
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return;
}
if (rnk[x] < rnk[y]) { //按秩合并
parent[x] = y;
} else {
parent[y] = x;
if (rnk[x] == rnk[y]) {
rnk[x]++;
}
}
}
bool issame(int x, int y) { return find(x) == find(y); }
void kruskal(void) {
ans = 0;
int cnt = 0; // 记录已经选用的边数
for (int i = 1; i <= m; i++) { //枚举m条边
//判断是否在一个集合中
if (!issame(e[i].u, e[i].v)) {
//没在集合中,就选中这条边,把u,v两点连起来
unite(e[i].u, e[i].v);
ans += e[i].w;
cnt++;
}
if (cnt == n - 1) { //选到了n-1条边了
break;
}
}
}
int main(void) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
}
sort(e + 1, e + m + 1, cmp); //按照边的权值排序
init();
kruskal();
printf("%d
", ans);
return 0;
}
Prim
Prim是针对点的最小生成树算法,任意选一个点出发,构成一个集合V,然后每次选择距离这个集合最近的点加入,当所有点都被加入集合V中时得到这棵树。使用优先队列和链式前向星优化的Prim算法的时间复杂度是(O((m+n)logm)),适用于稠密图
#include <bits/stdc++.h>
using namespace std;
int n; // n个点
int m; // m条边
int ans; //最小生成树的权值
int cnt; //边的index
const int MAXM = 2e5 + 5; //边的数据范围
const int MAXN = 5005; //点的数据范围
struct node {
int from; //和这条边同起点的上一条边的存储位置
int to; //这条边的终点
int w;
friend bool operator<(node a, node b) { return a.w > b.w; }
} e[MAXM << 1]; //无向图开两倍边
int head[MAXN]; // head[i]表示最后出现的以i为起点的边
bool vis[MAXN]; //访问过了的点
void add(int u, int v, int w) {
e[cnt].from = head[u]; //记录上次出现的边
e[cnt].to = v;
e[cnt].w = w;
head[u] = cnt++; //更新本次出现的边,然后cnt++
}
priority_queue<node> q;
void init() {
while (!q.empty()) q.pop();
memset(head, 0, sizeof(head));
memset(vis, 0, sizeof(vis));
cnt = 1;
ans = 0;
}
void prim() {
int now = 1; //从点1开始拓展
int tot = n - 1; //最多找n-1次
while (tot--) {
vis[now] = true; //记录已经访问过的点
//找以now点出发的所有边,将终点没有被访问的加入队列
for (int i = head[now]; i != 0; i = e[i].from) {
if (!vis[e[i].to]) {
q.push(e[i]);
}
}
node t = q.top(); //从当前的可拓展边中找出一条最短的
q.pop();
while (vis[t.to]) { //弹出所有已经被访问过的边
t = q.top();
q.pop();
}
now = t.to;
ans += t.w;
}
}
int main(void) {
scanf("%d%d", &n, &m);
init();
int u, v, w;
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
add(v, u, w);
}
prim();
printf("%d
", ans);
return 0;
}
以上是关于最小生成树两连的主要内容,如果未能解决你的问题,请参考以下文章