最小生成树-Prim算法和Kruskal算法
概念一 并查集:(union-find sets)
baby 一定要记得 哟 ——它是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。
由于感觉原作者博文写的挺好,所以直接粘上博主链接1
并查集的优化
1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过”递推”找到祖先节点后,“回溯”的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。
看图请点击链接1
2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
老板, 来一碗 “常见的简易Merge(函数)水煮肉片!!!”
int fin(int x) // 寻根 (可以递归,可以循环)
{
if(f[x]==x)
return x;
else
{
f[x]=fin(f[x]);
return f[x];
}
}
int Merge(int u,int v)
{
int t1,t2;
t1=fin(u);
t2=fin(v);
if(t1!=t2)
{
f[t2]=t1;///左边原则; 我就是看到这个词才粘的它
return 1;
}
else
return 0;
}
再来一碟 详细图解版的灌汤包
最小生成树-Prim算法和Kruskal算法链接2
图解过程很清晰。
part.2 题与代码
山东理工 ACM 链接
数据结构实验之图论九:最小生成树链接0
Time Limit: 1000 ms Memory Limit: 65536 KiB
Problem Description
有n个城市,其中有些城市之间可以修建公路,修建不同的公路费用是不同的。现在我们想知道,最少花多少钱修公路可以将所有的城市连在一起,使在任意一城市出发,可以到达其他任意的城市。
Input
输入包含多组数据,格式如下。
第一行包括两个整数n m,代表城市个数和可以修建的公路个数。(n <= 100, m <=10000)
剩下m行每行3个正整数a b c,代表城市a 和城市b之间可以修建一条公路,代价为c。
Output
每组输出占一行,仅输出最小花费。
Sample Input
3 2
1 2 1
1 3 1
1 0
Sample Output
2
0
Hint
Source
赵利强
思路:
如果想要让一个有n个点的图是连通图,那至少需要n-1条边,一个连通无向图且不含有回路那么他就是一个树,那么一个有n个点的图可以找到n-1条边保持它的连通性并且不含有回路,那么就找到了这个图的最小生成树(概念)
think:
找n-1条边,边的权值越小越好,那就进行升序排序,接下来就是判断当前边的两个顶点是否已经连通,以免有产生回路的情况。(该思路原作者博客链接 诚心学习, 顺便分享, 不是推广)
以下模块:
我所关注的学姐 写的代码讲解 她 的 博客
图结构练习——最小生成树(Prim+Kruskal)链接3
接下来就是关羽面前耍大刀喽
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#define INF 0x3f3f3f3f
#define MAXN 11111
using namespace std;
//Kruskal:以边来计算的,每次寻找最小的边,满足则加费用
struct node
{
int u, v, w;
}s[MAXN];
int pre[MAXN]; //存祖先
bool cmp(struct node x, struct node y)
{
return x.w < y.w;
}
int root(int a)
{
int r = a;
while(r != pre[r]) //寻找祖先节点
{
r = pre[r];
}
int b = a, c;
// 为了实现 路径压缩(可能元素不是很多, 没必要压缩吧)
while(b != r)
{
c = pre[b];
pre[b] = r;
b = c;
}
return r;
}
void Kruskal(int n, int m)
{
int sum = 0, ans = 0; // 统计输出完一次归零继下一次
for(int i = 0; i <= n; i++)
{
pre[i] = i; //初始化
}//初始化后每一个元素的父亲节点是它本身,
//每一个元素的祖先节点也是它本身(也可以根据情况而变)
for(int i = 0; i < m; i++) //遍历每一条边
{
int x = root(s[i].u);
int y = root(s[i].v);
if(x != y)//判断两个元素是否属于同一集合,只要看其所在集合的祖先是否相同即可
{
ans += s[i].w;
pre[x] = y;
sum += 1;
if(sum == n - 1)
break;
}
}
printf("%d\n", ans);
}
int main()
{
int n, m;
while(~scanf("%d %d",&n, &m))
{
for(int i = 0; i < m; i++)
{
scanf("%d %d %d", &s[i].u, &s[i].v, &s[i].w);
}
sort(s, s + m, cmp); // 先排序,sort 比qsort 里面写的少点
Kruskal(n, m);
}
return 0;
}
注释多了很难看。。。。。。。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#define MAXN 11111
using namespace std; //Kruskal:以边来计算的,每次寻找最小的边,满足则加费用
struct node
{
int u, v, w;
}s[MAXN];
int pre[MAXN]; //存祖先
bool cmp(struct node x, struct node y)
{
return x.w < y.w;
}
int root(int a)
{
int r = a;
while(r != pre[r]) //寻找祖先节点
{
r = pre[r];
}
int b = a, c; // 为了实现 路径压缩(可能元素不是很多, 没必要压缩吧)
while(b != r)
{
c = pre[b];
pre[b] = r;
b = c;
}
return r;
}
void Kruskal(int n, int m)
{
int sum = 0, ans = 0; // 统计输出完一次归零继下一次
for(int i = 0; i <= n; i++)
{
pre[i] = i; //初始化
} //初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)
for(int i = 0; i < m; i++) //遍历每一条边
{
int x = root(s[i].u);
int y = root(s[i].v);
if(x != y) //判断两个元素是否属于同一集合,只要看其所在集合的祖先是否相同即可
{
ans += s[i].w;
pre[x] = y;
sum += 1;
if(sum == n - 1)
break;
}
}
printf("%d\n", ans);
}
int main()
{
int n, m;
while(~scanf("%d %d",&n, &m))
{
for(int i = 0; i < m; i++)
{
scanf("%d %d %d", &s[i].u, &s[i].v, &s[i].w);
}
sort(s, s + m, cmp); // 先排序,sort 比qsort 里面写的少点
Kruskal(n, m);
}
return 0;
}