并查集是什么
首先看一下wikipedia中是怎么解释的并查集的
在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
- Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-findset)。其他的重要方法,MakeSet,用于创建单元素集合。有了这些方法,许多经典的划分问题可以被解决。
看完虽然半懂不懂,但是可以看出来有两个操作Find和Union,这也是名字的来源,合并和查找。
那这两个操作有什么用呢?
并查集的小例子
超可爱的并查集~(严谨标注到符号)这里面提到了一个很简单的小例子,看完就有那么一点懂了。
虽然里面有提到杭电其中的畅通工程一题,但是我还没写(实际无账号)所以就直接讲题目与例子。看完可以去试一试。
- 题目(简略版)
有标号1234的四个点,其中已经有两条线了,分别是1-3与4-3。就和下图一样。需要是的所有点都互相连通(就是按着线可以走到)。
- 解法
肉眼看一下,2和哪一个点连线,就都可以走到了。
看到这里基本懂了并查集是干啥的,就是先找到哪个最值得连,然后把没连通的连起来。
但是,如果有很多很多个点,肉眼看不出来怎么办,比如下图这样。
这时候就可以用代码了。
伪码
先看伪代码,还是wikipedia上来的。
基础的并查集伪码
初始化操作,使每个根节点的父节点都为其自身。
function MakeSet(x)
x.parent := x
Find操作(查找操作),运用递归一直向上找到x的根节点(数据结构里树的概念),改成循环是一样的。个人理解是可以通过根节点走到任意子节点。
function Find(x)
if x.parent == x
return x
else
return Find(x.parent)
Union操作(合并操作),找到两个节点的根节点之后,就把这两个节点连起来(不用判断是不是同一个根节点,因为初始化一定保证了根节点的父节点是其自身),之后整个图就连通了。
function Union(x, y)
xRoot := Find(x)
yRoot := Find(y)
xRoot.parent := yRoot
上面讲的是基础并查集,wikipedia有提到两种优化算法。
按秩合并伪码
改进的原因是基础的方法创建的树可能会严重不平衡,那么按秩合并就是把小的树连到大的树上,可以相对保持平衡。
秩可以理解成树的深度。
初始化操作,除了根节点的父节点为其自身之外,还将秩设为了0。
function MakeSet(x)
x.parent := x
x.rank := 0
Find操作是没有变的,Union操作和基础的有区别。
Union操作,可以看到增加了秩的判断,如果不在同一个集合,就将秩小的树合并到秩大的树上。如果两棵树的秩相等,则任意合并并且将秩+1。
function Union(x, y)
xRoot := Find(x)
yRoot := Find(y)
if xRoot == yRoot
return
// x和y不在同一个集合,合并它们。
if xRoot.rank < yRoot.rank
xRoot.parent := yRoot
else if xRoot.rank > yRoot.rank
yRoot.parent := xRoot
else
yRoot.parent := xRoot
xRoot.rank := xRoot.rank + 1
路径压缩伪码
针对Find操作进行了改进,将所有子节点都连接到了根节点,加速了之后的节点操作。
function Find(x)
if x.parent != x
x.parent := Find(x.parent)
return x.parent
C++代码
暂时不想写(危险发言),先贴一个超可爱的并查集~里的。(我不生产代码,我只是代码的搬运工)
用到了路径压缩,大家对注释感兴趣的可以去原博主那里看看门派的故事。
#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]) //寻找掌门ing……
root = pre[root];
while(son != root) //路径压缩
{
tmp = pre[son];
pre[son] = root;
son = tmp;
}
return root; //掌门驾到~
}
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;
把代码复制带来的声明放一下,很感谢这位优秀的博主带我学习。
————————————————
版权声明:本文为CSDN博主「飘过的小牛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/niushuai666/article/details/6662911