并查集入门的个人总结

并查集是什么

首先看一下wikipedia中是怎么解释的并查集

在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:

  • Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  • Union:将两个子集合并成同一个集合。

由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-findset)。其他的重要方法,MakeSet,用于创建单元素集合。有了这些方法,许多经典的划分问题可以被解决。

看完虽然半懂不懂,但是可以看出来有两个操作FindUnion,这也是名字的来源,合并和查找。
那这两个操作有什么用呢?

并查集的小例子

超可爱的并查集~(严谨标注到符号)这里面提到了一个很简单的小例子,看完就有那么一点懂了。
虽然里面有提到杭电其中的畅通工程一题,但是我还没写(实际无账号)所以就直接讲题目与例子。看完可以去试一试。

  1. 题目(简略版)
    有标号1234的四个点,其中已经有两条线了,分别是1-3与4-3。就和下图一样。需要是的所有点都互相连通(就是按着线可以走到)。
    并查集例子1
  2. 解法
    肉眼看一下,2和哪一个点连线,就都可以走到了。
    看到这里基本懂了并查集是干啥的,就是先找到哪个最值得连,然后把没连通的连起来。
    但是,如果有很多很多个点,肉眼看不出来怎么办,比如下图这样。
    并查集例子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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值