并查集是算法中非常重要的概念也是常用的一个结构
网上看到我觉得最好的是https://blog.csdn.net/niushuai666/article/details/6662911,也就是我参考的博文
我用自己的语言删减了一下进行记录。
并查集的概念
并查集可以生动的用下图表示。
可以看到是一颗颗的树(图),任意两点可以相互访问的是一类。
一般来说,每个点只知道自己的父节点是什么。最远祖先(树的根节点)的父节点是自身。最远祖先相同的两个节点属于同一个集合。
两个集合合并,只需要两个点分别来自两个集合,彼此可以访问,那两个集合就都联通了。
算法实现
那么算法需要实现的部分:
pre[]数组: 用于储存这个节点的父节点。
int uninonsearch(int root) :查找根节点
void join(int root1, int root2) :将root1和root2所属的两个集合合并
因为树可能会变得特别深,不利于查找,所以需要一个路径压缩算法实现下图:其实就是在寻找根节点过程中途径的所有节点的父亲设置为根节点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int pre[1010]; //记录每个点的父节点
int unionsearch(int root)
{
int son, tmp;
son = root;
while(root != pre[root]) //寻找根节点
root = pre[root];
while(son != root) //已经找到了根节点,进行路径压缩
{
tmp = pre[son];
pre[son] = root;
son = tmp;
}
return root; //返回根节点
}
void join(int root1, int root2) //合并函数
{
int x, y;
x = unionsearch(root1);//找到根节点
y = unionsearch(root2);//找到根节点
if(x != y)
pre[x] = y; //合并,就是其中一个的父节点换为另一个
}
int main()
{
int num, road, total, i, start, end, root1, root2;
while(scanf("%d%d", &num, &road) && num)
{
total = num - 1; //共num-1个门派
for(i = 1; i <= num; ++i) //每条路都是掌门(自己自成一棵树)
pre[i] = i;
while(road--)
{
scanf("%d%d", &start, &end); //他俩要结拜
root1 = unionsearch(start);
root2 = unionsearch(end);
if(root1 != root2) //掌门不同?合并!
{
pre[root1] = root2;
total--; //门派少一个,总的集合数减一
}
}
printf("%d\n", total);//天下局势:还剩几个门派
}
return 0;
}
实例
题目链接
描述:
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入:
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
解释:
首先根据城市数量初始化有N个集合(每个城市自身为一个)。根据有相互之间有路,来合并集合,最后得到还剩下多少集合(T个)。那就需要建立T-1条路。