算法笔记之旅 | 并查集

概念

并查集虽然是个进阶数据结构,但是其思想却很简单。它主要用来维护集合以及处理部分图相关的题目(相对来说很多题目都可以抽象成一个图论题)。它支持Union、Find两种操作:

  • 合并:合并两个集合
  • 查找:判断两个元素是否在同一个集合中

并查集定义

维护这个操作只需要一个数组来保存每一个元素对应的父节点:

int father[N];

father[i]代表第i个元素的父节点,比如说father[1]=2,表示元素1的父亲是元素2。对一个集合来说,其根节点也就是祖宗节点存在这个特点:father[i]==i。如下图,2是3和4的父节点,1是2的父节点。5是6的父节点。
在这里插入图片描述

并查集基本操作

基本操作也就3个,分别如下:

初始化

初始化很简单,只需要把father数组的每一个值赋给自己就行:

for(int i=1;i<=N;i++)
{
	father[i]=i;
}

查找

查找所要实现的功能是找到x元素的根节点。对于查找而言,其实就是一个不断重复的过程,比如要查找一个节点5的最终父节点:

  1. 首先判断当前节点是否是根节点:要满足father[x]==x,不满足就循环1和2
  2. x=father[x];把father[x]赋值给x,递推就可以了
int findFather(int x)
{
	while(x!=father[x])
	{
		x=father[x];
	}
	return x;
}

这个过程类似于链表的查找尾结点。因为是遍历,其时间复杂度是0(n)

合并

对于合并而言,就是把两个集合合并成一个,也就是说把两个集合的父节点设置为同一个。遵循这个思路:

  1. 查询A的根节点
  2. 查询B的跟节点
  3. 判断两个节点是否相等,不相等就把B的值赋值给father[A]
void Union(int a,int b)
{
	int father_a=findFather(a);
	int father_b=findFather(b);
	if(father_a!=father_b)
	{
		father[father_a]=father_b;
	}
}

基于上面查找操作的合并操作的时间复杂度也是0(n),但是可以优化,如下:

路径压缩

显然,查找操作的时间复杂度有点高,极端情况下需要遍历,0(n)的时间复杂度。事实上查找函数只是为了找跟节点,链式查找没有必要,中间信息没有作用,不如直接把中间的所有节点的父节点全部设置为根节点。如下:
在这里插入图片描述
这样,在查找的时候,其时间复杂度就几乎变成了O(1)

递推实现:

int findFather(int x)
{
	//1.首先找到x的根节点
	int a=x;   //留存等下要用,x会发生变化
	while(x!=father[x])
	{
		x=father[x];
	}
	//目前x保存着根节点的值
	while(a!=father[a])   //再来一次从头遍历
	{
		int z=a;       //同理也是为了留存
		a=father[a];   //a等于父节点
		father[z]=x;   //把上一个节点的父节点赋值成x,也就是跟节点
	}
	//2.把之前所有节点的父节点设置成根节点
}

递归写法:

int findFather(int x)
{
	if(x==father[x]) return x;
	else 
	{
		int fa=findFather(x);
		father[x]=fa;
		return fa;
	}
}

总结

并查集是一个维护对集合进行查找、合并的数据结构,经过优化后,其查找与合并的时间复杂度为O(1)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值