普通并查集

(一)普通并查集

普通并查集由三部分组成:
(1)一个整型数组
(2)初始化函数,inin
(3)一个查找函数,find
(4)一个合并函数,join
—>1.1 初始化,inin
使每个元素自身成为一个集合,即pre[i]=i。

 void inin(int x)		//初始化
{
	int i;
	for (i = 0; i <= x; i++)
	{
		pre[i] = i;
		rank1[i] = 1;
	}
}

–>1.2 查找函数,find
查找一个元素的根节点。

 int find(int x)
{
	int r;
	r = x;
	while (r != pre[r])		//它的节点不是他本身时。
	{
		r = pre[r];
	}
	return r;             //返回根节点
}

–>1.3 合并函数,join
把两个元素的根节点合并,形成树

void join(int a, int b)
{
	int fa;
	int fb;
	fa = find(a);		//查找a的跟节点
	fb = find(b);		//查找b的跟节点
	if (fa != fb)		//当a和b根节点不同时,把他们合并
	{
		pre[fa] = fb;
	}
}

到这里,我们会发现,在执行上面的代码后,形成的树的深度可能会太大,一字长蛇阵也是有可能的,这样查找起来的效率就会很低,当然,在一些题中,数据量小,效率就无所谓,但在一些数据量非常大的题中,效率就很重要。而且形成的树可能会退化。为了避免这个问题,我们对代码做一些改进。
压缩路径:
意思就是让所有的节点都指向根节点,使树的层数在维持一种较低的水平,从而提高查找效率。
在这里插入图片描述
代码实现:

#define N 55005
int pre[N];			//树的节点
int rank1[N];		//树的深度
int n, m;
void inin(int x)		//初始化
{
	int i;
	for (i = 0; i <= x; i++)
	{
		pre[i] = i;
		rank1[i] = 1;
	}
}
int find(int x)		 //查找结点x的根结点
{
	if (pre[x] == x)
	{				 //递归出口:x的上级为x本身,即x为根结点
		return x;
	}
	else
		return pre[x] = find(pre[x]);   //递归查找  此代码相当于 先找到根结点rootx,然后pre[x]=rootx
}

void join(int a, int b)		//节点和并
{
	int fa;
	int fb;
	fa = find(a);
	fb = find(b);
	if (fa == fb)
		return ;
	else if (rank1[fa] > rank1[fb])
	{
		pre[fb] = fa;
	}
	else
	{
		if (rank1[fa] == rank1[fb])
		{
			rank1[fb]++;
		}
		pre[fa] = fb;
	}
}

例题:

在三角洲不同地区生活的人们有自己信仰的图腾,为了避免冒犯到他们,首脑们现在想知道他们最多可能有多少种图腾。但这对他们来说是件无趣的事,所以现在任务交到了你的身上。 当你看到两个地区的人们在膜拜同一外观的石像时,就可以断定他们拥有同样的图腾。已知三角洲共有n(n <= 50000)块区域,你看到了m(m<=n(n-1)/2)组地区的人们在膜拜同样的石像,现在请问他们至多有多少种图腾。

Input
有多组数据。对于每组数据:
第一行:两个整数n和m。
以下m行:每行包含两个整数i和j,表示你发现i地区和j地区的人们在膜拜同样的石像。地区编号从1到n。
输入的最后一行中,n = m = 0。

Output
对于每组测试数据,输出一行,输出数据序号( 从1开始) 和图腾的最大数量。(参见样例)

Sample Input
10 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4
2 3
4 5
4 8
5 8
0 0

Sample Output
Case 1: 1
Case 2: 7

#include<iostream>
#include<cstdio>
using namespace std;
#define N 55005
int pre[N];			//树的节点
int rank1[N];		//树的深度
int n, m;
int sum;
void inin(int x)		//初始化
{
	int i;
	for (i = 0; i <= x; i++)
	{
		pre[i] = i;
		rank1[i] = 1;
	}
}
int find(int x)		 //查找结点x的根结点
{
	if (pre[x] == x)
	{				 //递归出口:x的上级为x本身,即x为根结点
		return x;
	}
	else
		return pre[x] = find(pre[x]);   //递归查找  此代码相当于 先找到根结点rootx,然后pre[x]=rootx
}

void join(int a, int b)		//节点和并
{
	int fa;
	int fb;
	fa = find(a);
	fb = find(b);
	if (fa == fb)
		return ;
	else if (rank1[fa] > rank1[fb])
	{
		pre[fb] = fa;
		sum--;
	}
	else
	{
		if (rank1[fa] == rank1[fb])
		{
			rank1[fb]++;
		}
		pre[fa] = fb;
		sum--;
	}
}
int main()
{
	
	int a, b;
	int k=0;
	while (cin >> n >> m&&n&&m)
	{
		k++;
		inin(n);
		sum = n;
		while (m--)
		{
			cin >> a >> b;
			join(a, b);
		}
		cout << "Case ";
		cout << k << ':';
		cout <<" "<< sum << endl;
	}
	return 0;
}

在这里插入图片描述
并查集算法的用途;
1、维护无向图的连通性。支持判断两个点是否在同一连通块内,和判断增加一条边是否会产生环。
2、用在求解最小生成树的Kruskal算法里。
下面是大佬对并查集的讲解:
普通并查集

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值