并查集(详细解释+完整C语言代码)

这篇具有很好参考价值的文章主要介绍了并查集(详细解释+完整C语言代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1概论

2.树的表现形式

3.代码实现

3.1创立集合

3.2合并

3.3查询

3.4路径压缩

第一个方法:查找时优化

第二个方法:合并时优化(加权标记法)

4.完整代码

4.1优化前

 4.2优化后


1概论

并查集是一种十分精巧且实用的树形数据结构,它主要处理一些不相交集合的合并与查询问题。

  • 不相交集合:即两集合交集为空。集合(1,2,3)与集合(4,5,6)不相交;
  • 合并:将两不相交集合合并为一个集合。集合被合并为(1,2,3,4,5,6)
  • 查询:查找此元素所处集合以及代表,可用于判断两元素是否同属一个集合

每个集合里有很多元素,我们选取其中一个元素作为此集合的代表。

  • 我们不关心这个集合中具体包含了哪些元素;
  • 我们不关心选哪个元素作为代表;
  • 我们关心的是,给定一个元素,可以很快找到这个元素所在集合的代表;
  • 我们关心的是,给定两个元素,判断他们是否处于同一个集合,如果不是,可以将两元素分别所处集合进行合并。

我们为什么关心这些,不关心那些呢?

因为这就是并查集的精妙之处,它可以解决很多实际问题。比如下面两道题:

并查集代码,数据结构,数据结构,算法,c语言

并查集代码,数据结构,数据结构,算法,c语言

我们以第二题为例,可以看出:只有两人之间(A与B)知道彼此是朋友,A不知道B与其他人的关系,B也不知道A与其他人的关系,甚至他们也不知道可能拥有共同的朋友C。题目假设朋友的朋友就是自己的朋友,相当于这群人是一伙的。现在,我想知道哪些人是一伙的。并查集就能轻松解决这个问题。

2.树的表现形式

并查集的实现原理也比较简单,就是用树来表示一个集合,树的每个结点就是集合的一个元素,根节点就是集合的代表。

并查集代码,数据结构,数据结构,算法,c语言

 例如上图的两棵树,就对应的两个集合。我们继续用“朋友”的例子进行说明:刚才的朋友还知道彼此是朋友。而并查集实现时,就最多只知道自己的一个朋友,且不能是彼此知道。例如:D知道他的朋友是B,但B不知道D,只知道他的朋友是A。

以第一个集合为例:

  • D知道他的朋友为B,E知道他的朋友也为B,但D、E互相不认识;
  • B知道他的朋友为A,C知道他的朋友也为A,但B、C互相不认识,同时B也不知他还有朋友D、E;
  • A作为这个集合的代表,只知道自己,不知道他“下面”的朋友们。

尽管A~E是一伙的,他们却最多知道自己的一个朋友?那怎样才能让两人相见时,知道彼此是不是一伙的呢?

答案是:查询

除了代表(根节点),每个人都知道自己的一个朋友,那我们就可以让自己的朋友去问他的朋友,直到自己朋友的朋友...的朋友是这个集合的代表。由于代表(根节点)只有一个,而两人都知道自己和同一个代表是一伙的,那显然两人也是一伙的。这也就是查询的逻辑。

  • 例1:D和C他们是一伙的吗?我们不知道,于是D问他的朋友B,B不是代表,B又去问他的朋友A,A是代表,好了,不用问了;C问他的朋友A,A是代表,不用问了。最终的代表都是A,显然,D和C是一伙的。
  • 例2:E和H他们是一伙的吗?我们也不知道,于是E问他的朋友B,B不是代表,B又去问他的朋友A,A是代表,不用再问了;H问他的朋友G,G不是代表,G问他的朋友F,F是代表,不用再问了。显然,最后的代表不一样,他们不是一伙的。

现在又有个问题,E和H原本不是一伙的,但现在他们交朋友了,根据自己朋友的朋友都是朋友的逻辑,那显然上面两伙人就是一伙人了。这时,他们只要选择一个代表作为他们整体的代表就行了。这种情况叫什么呢?

答案是:合并(两集合合并为一个集合)

他们合并方式可以有很多种,比如让第二伙人的代表F与E交朋友(E作为F的父节点),也可以让F直接和第一伙的代表A交朋友(A作为F的父节点)。

并查集代码,数据结构,数据结构,算法,c语言

他们怎么交朋友,当然无所谓。但它作为数据结构,会影响到我们算法(特别是查询)的效率,所以怎么交朋友其实是有考究的。显然第二种就比第一种好。因为用第二种方法,H、G、F等找到所在集合代表的速度就更快。

在实现的过程中,我们就希望提高算法的效率,所以就需要路径压缩

3.代码实现

在讲清楚逻辑后,我们就需要看看代码怎么实现了。并查集虽然是树形结构,但他只用数组就可以实现。

3.1创立集合

现在,假设使用一个足够长的数组来存储树结点,那么创立集合就是构造如下森林,其中每个元素都是一个单元素集合,即父节点为自身。

并查集代码,数据结构,数据结构,算法,c语言

void makeSet(int size)
{
	for (int i = 1; i <= size; i++)
	{
		//各自为各自的代表
		uset[i] = i;
	}
}

 即:uset[1]=1,uset[2]=2,uset[3]=3,uset[4]=4,uset[5]=5,uset[6]=6。这意味着,uset[i]的朋友(父节点)是i自己。

3.2合并

例如,合并(1,2,3,4),合并(5,6),分别以1和5作为代表

并查集代码,数据结构,数据结构,算法,c语言

void unite(int x, int y) 
{
	//先找相对应的代表
	int x = find(x);
	int y = find(y);
	if (x == y)
	{
		return;
	}
	uset[x] = y;
}
  1.  合并2和1:分别找到自己的代表,都是自己2和1,显然2!=1,则uset[2]=1,这意味着,uset[2]的朋友是1;
  2. 合并3和2:分别找到自己的代表,都是自己3和2,显然3!=2,则uset[3]=2,这意味着3的朋友是2;
  3. 合并4和3:分别找到自己的代表,都是自己4和3,显然4!=3,则uset[4]=3,这意味着4的朋友是3;

这样(1,2,3,4)合并完成,此时可以发现它是一棵斜树

同理,合并(5,6)就不再赘述。

现在,我们假设再合并(4,6)。

合并6和4:分别找到自己的代表5和1,显然5!=1,则uset[5]=1,这意味着5的朋友是3(1作为5的父节点)。

并查集代码,数据结构,数据结构,算法,c语言

当然,以上既可以是uset[x]=y,也可以是uset[y]=x。只要他们每次坚持一样的规则就行了,我们并不关注谁是父结点,谁是最后的根结点(代表)。 

3.3查询

在上面的合并代码中,出现了find(x),他其实就是查找x的代表是谁。

查询采用递归的思想,直到代表是自己(访问到根结点),否则就一直找朋友的朋友...find(uset[i])

int find(int i)
{
	if (i == uset[i])
	{
		return i;
	}
	return find(uset[i]);
}

3.4路径压缩

在3.2中,如果采用此方法,最后就会成为一棵斜树。那么在查询时所耗费的时间就变多了,那有没有办法减少所用时间呢?

第一个方法:查找时优化

在每次查找时,令查找路径上的每个结点都指向根结点。

并查集代码,数据结构,数据结构,算法,c语言

 原先,4找代表1需要3次,现在只需要1次啦!

int find(int i)
{
	if (i == uset[i])
	{
		return i;
	}
	return uset[i] = find(uset[i]);//在第一次查找的时候 将结点直接连到跟
}
  • 4找代表:代表不是自己,就一直找到根结点,并将根结点1作为4的父节点(直接朋友)
  • 3找代表:代表不是自己,就一直找到根结点,并将根结点1作为3的父节点(直接朋友)
  • 2找代表:代表是根结点了,不用改变
  • 1找代表:代表是根结点了,不用改变

第二个方法:合并时优化(加权标记法)

我们回忆一下3.2中的合并,无论如何,我们都坚持着一个原则:uset[x]=y,即始终是前面那个数x作为孩子结点,后面那个数y作为x的父节点,最后合并成为了一棵斜树。

那如果我们加入一个判断呢?

如果x作为父节点更好,就让x当父亲,否则,就让y当父亲(结点)。

判断的依据是什么呢?

此时,我们要给每个结点加一个属性,俗称权值,即此结点的高度。

并查集代码,数据结构,数据结构,算法,c语言

例如合并x和y

  • 若R[x]>R[y],则uset[y]=x;
  • 若R[x]<R[y],则uset[x]=y;
  • 若R[x]==R[y],则都可以。

当合并1和5时,由于R[1]>R[5],则uset[5]=1,让5的父节点为1。否则,就会让树的高度增加,查询效率就降低了。

根据以上信息可以得到:让权值更大的结点作父亲

void unite(int x, int y)
{
	//先找相对应的代表
	int x = find(x);
	int y = find(y);
	if (x == y)
	{
		return;
	}
	//判断两棵树的高度 在决定谁为子树
	if (rank[x] < rank[y])
	{
		uset[x] = y;
	}
	else
	{
		uset[y] = x;

		if (rank[x] == rank[y])
		{
			rank[x]++;
		}
	}

当然,如果两结点权值相同,谁做父节点都可以,但此时,作父节点的那个结点权值就要增加1。

4.完整代码

这里只给出了模板,就没有写主函数啦。做题时只需套用模板即可。文章来源地址https://www.toymoban.com/news/detail-840377.html

4.1优化前

#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100

int uset[MAXSIZE];//定义一个足够长的数组
//用下标来表示结点
/*
构造并查集
int size 有多少个节点
*/
void makeSet(int size)
{
	for (int i = 1; i < size; i++)
	{
		//各自为各自的代表
		uset[i] = i;
	}
}

/*
找到元素所在的集合的代表 如果位于同一集合 
*/
int find(int i)
{
	if (i == uset[i])
	{
		return i;
	}
	return find(uset[i]);
}

void unite(int x, int y) 
{
	//先找相对应的代表
	int x = find(x);
	int y = find(y);
	if (x == y)
	{
		return;
	}
	uset[x] = y;

}

 4.2优化后

#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
/*
因为在特殊情况下 这棵树可能是一个长长的树链 设链的最后一个节点为x
每次执行find 相当于遍历整个链条
只需要把便利过的结点都改成跟的子节点 查询就会快很多
*/


int uset[MAXSIZE];//定义一个足够长的数组 每个结点
int rank[MAXSIZE];//树的高度

//用下标来表示结点
/*
构造并查集
int size 有多少个节点
*/
void makeSet(int size)
{
	for (int i = 1; i <= size; i++)
	{
		//各自为各自的代表
		uset[i] = i;
		//树的高度
		rank[i] = 0;
	}
}

/*
找到元素所在的集合的代表 如果位于同一集合
查找元素在哪个集合
*/
int find(int i)
{
	if (i == uset[i])
	{
		return i;
	}
	return uset[i] = find(uset[i]);//在第一次查找的时候 将结点直接连到跟
}

void unite(int x, int y)
{
	//先找相对应的代表
	int x = find(x);
	int y = find(y);
	if (x == y)
	{
		return;
	}
	//判断两棵树的高度 在决定谁为子树
	if (rank[x] < rank[y])
	{
		uset[x] = y;
	}
	else
	{
		uset[y] = x;

		if (rank[x] == rank[y])
		{
			rank[x]++;
		}
	}
}

到了这里,关于并查集(详细解释+完整C语言代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】并查集

    并查集是简单的数据结构,学会并查集,为图打好基础。 是树状的数据结构,用于处理相交集合的合并与查询 通常用森林表示,一片森林表示一个集合 并查集一般需要完成 查找元素属于哪个集合 查看两个元素是否属于同一个集合 将两个集合归并成一个集合 集合的个数 假

    2024年02月19日
    浏览(36)
  • 【高阶数据结构】——并查集

    在一些应用问题中, 需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合, 然后按一定的规律将归于同一组元素的集合合并 。在此过程中要 反复用到查询某一个元素归属于那个集合的运算 。适合于描述这类问题的抽象数据类型称为 并查集

    2024年02月16日
    浏览(37)
  • 数据结构--并查集

    所有元素的全集s 将各个元素划分为若干个互不相交的子集 用一个数组S[ ]即可表示“集合”关系 集合的两个基本操作―— “并” color{red}“并” “ 并 ” 和 “查” color{red}“查” “ 查 ” Find -—“查”操作:确定一个指定元素所属集合 Union --“并”操作:将两个不想交的集

    2024年02月15日
    浏览(36)
  • 数据结构---并查集

    这里可以使用生活中的一个例子来带着大家理解并查集,大家在上学的过程中肯定会发现一种现象,在开学之前大家谁也不认识谁每个人都是一个小团体,可是开学之后因为座位旁边有一个同桌,所以开学没多久你和同桌就会互相认识并且开心的玩在一起,那么这时就是两个

    2024年02月15日
    浏览(40)
  • 高阶数据结构 ——— 并查集

    并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。 并查集通常用森林来表示,森林中的每棵树表示一个集合,树中的结点对应一个元素。 说明一下: 虽然利用其他数据结构也能完成不相交集合的合并及查询,但在数据量极大的情况下,其耗费的时

    2024年02月03日
    浏览(52)
  • 数据结构之并查集

    并查表原理是一种 树型的数据结构 ,用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的树根,就能确定它在哪个集合里。这类问题的抽象数据类型称为并查集(uni

    2024年02月12日
    浏览(36)
  • 【数据结构与算法】并查集

    并查集是一个树形结构,所谓的并查,就是 当我们有了一个节点,我们就能知道这个节点属于哪个集合 。 举个例子理解以下:战国时期有很多国家,它们会互相打仗,那么现在有两个互相不认识的人,如何知道对方是敌是友呢? 现在有一种方法:每个国家都有一个大王,我

    2023年04月15日
    浏览(36)
  • 算法与数据结构(九)--并查集

    1.处理对象:Disjoint Set,即“不相交集合”。 在一些应用问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定顺序将属于同一组元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的

    2024年02月10日
    浏览(40)
  • 【数据结构】| 并查集及其优化实现

    以一个直观的问题来引入并查集的概念。 亲戚问题:有一群人,他们属于不同家族,同一个家族里的人互为亲戚,不同家族的人不是亲戚。随机指定两个人,问他们是否有亲戚关系。 以下图3个不相交的集合表示 3 个家族,当询问两个人是否有亲戚关系时,也就是问两个元素

    2024年02月09日
    浏览(38)
  • 数据结构(八):并查集详解 (多图+动图)

    目录 一、什么是并查集 二、并查集的存储结构 三、并查集的基本操作 (一)初始化 (二)Find操作 (三)Union操作 四、并查集的优化 (一)Union操作优化(小树并入大树) (二)Find操作优化(压缩路径)         并查集的逻辑结构是一个包含N个元素的 集合 ,如图:

    2024年02月03日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包