数据结构之并查集总结篇(C语言)

前言

小白发言,整理资料总结了一下并查集的做法步骤,有错误之处敬请指正。

并查集题目常见背景

  • 想查清楚有几个犯罪团伙,会给出强盗人数线索的条数。(题干内容n,m)
  • 线索指的是:xx和xxx是同伙为一个线索。例如:给出A是B的同伙,此时可以根据这个线索判断A,B的boss是不是一个人,不是的话进行合并。

并查集题目的主要目的

  • 查找——挨个问团中人员他们老大是谁,由老大来定几个独立的团伙。
  • 合并——利用题中的线索将各团伙人员规整,团里每个人都指向老大。

并查集的两个原则(包含路径优化)

  • 靠左法则:如果不是一个boss,将左边的认为是最大的boss来确定二者合并的最大boss。
  • 擒贼先擒王:即不停地去找上级,直到找到老大为止,其实就是去找犯罪团伙的最高领导人。(通过将每个人的上级设置为最终大BOSS来达到路径优化的目的。)

并查集题目的基本思路

1、首先进行初始化每个人对应的boss设置为自己
2、 之后根据题目给出的条件(比如1号强盗与2号强盗是同伙),将每条同伙信息通过合并函数(merge)进行合并,(通过查找各自的boss来确定,如果不是一个boss,根据靠左法则,即将左边的认为是最大的boss来确定二者合并的最大boss)
3、 最后可以通过遍历每个强盗存储的大boss名(其他小喽啰的boss不是自己,而大boss的boss是自己来确定是一个团伙,即确定boss个数来确定有几个团伙)来确定有几个独立的团伙

代码

#include<stdio.h>
int f[1000]={0},n,m,k;

//这里是初始化,每个人对应的boss设置为自己。
void init(){
    for(int i=0;i<n;i++){
        f[i]=i;
    }
}

//这是找爹的递归函数,不停地去找爹,直到找到祖宗为止,其实就是去找犯罪团伙的最高领导人,擒贼先擒王"原则。
//int getf(int v)
//{	
//	if(f[v]==v)
//	return v;
//else{
//这里是路径压缩,每次在函数返回的时候,顺带把路上遇到的人的"BOSS"改为最后找到的祖宗编号,也就是犯罪团伙的最高领导人编号。这样可以提高今后找到犯罪团伙的最高领导人(其实就是树的祖先)的速度。
//	f[v]=getf(f[v]);
//	return f[v];
//	}
//}

//写法二:(思路与上述一样,代码改进)(也一般称为find函数)
int getf(int v) {
    return v == f[v] ? v : getf(f[v]);  
    }


//这里是合并两子集合的函数
void merge(int v,int u)
{
	int t1,t2;
	t1=getf(v);
	t2=getf(u);
	if(t1!=t2)//判断两个结点是否在同一个集合中,即是否为同一个祖先。
	{
	f[t2]=t1;
	//“靠左”原则,左边变成右边的BOSS。即把右边的集合,作为左边集合的子集合。
	//经过路径压缩以后,将u的根的值即f[t2]也赋值为v的祖先t1。
	}
}

// 判断两个节点是不是同一个集合的(不是必写项)
bool same(int x, int y) { 
        return find(x) == find(y);
    }


int main()
{
	int i,x,y;
	scanf("%d %d",&n,&m);	//读取人数和线索数
	init();					//初始化每个人的boss
	for(i=0;i<m;i++)
	{
		scanf("%d %d",&x,&y);
		merge(x,y);			//根据线索来合并犯罪团伙
	}


	//最后扫描有多少个独立的犯罪团伙
	for(i=0;i<n;i++)
	{
		if(f[i]==i)		//即找到几个老大
		sum++;
	}
	printf("%d\n",sum);
	return 0;
}

具体问题具体分析:

力扣547. 省份数量
参考答案:多图详解并查集
547. 省份数量:【并查集】基础题目

  • 题目分析——需要找的是省份个数,也即查找的是团伙个数。
  • 本题给出的是一个n*n的矩阵,与线索有所区别。
  • 题中为一个邻接矩阵,邻接矩阵中行列表示存在一条连接,表示可达。即题目中写的 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连(该条信息( isConnected[i][j] = 1 )等同于一条线索)。将每条线索进行合并即可得到有几个独立团伙。
int f[1000]={0},n=0;
void init(){
    for(int i=0;i<n;i++){
        f[i]=i;
    }
}
int getf(int v) {
    return v == f[v] ? v : getf(f[v]);  
    }

void merge(int v,int u)
{
	int t1,t2;
	t1=getf(v);
	t2=getf(u);
	if(t1!=t2)//判断两个结点是否在同一个集合中,即是否为同一个祖先。
	{
	f[t2]=t1;

	}
}

int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize){
    n=isConnectedSize;
    init();					//初始化每个人的boss
	for(int i=0;i<n;i++){
        for (int j = i + 1; j < n; j++) {
		if(isConnected[i][j]==1)
        {merge(i,j);}			//根据线索来合并犯罪团伙
	}
    }
    int sum=0;
    for(int i=0;i<n;i++)
	{
		if(f[i]==i)		//即找到几个老大
		sum++;
	}
    return sum;
}

并查集优化思路

  • 根据上面的靠左原则,可能会出现深度(树的高度)不断变大的情况,因此可以记录两者树的深度,优先将高度低的节点指向高度高的根节点。
  • 优化后的程序如下:
#include<stdio.h>
int f[1000]={0}rank[1000]={0},n,m,k;

//这里是初始化,每个人对应的boss设置为自己。
void init(){
    for(int i=0;i<n;i++){
        f[i]=i;
        rank[i]=1;	//以该元素为根节点的树的深度就为1
    }
}

int getf(int v) {
    return v == f[v] ? v : getf(f[v]);  	//查询该元素的根节点
    }


//这里是合并两子集合的函数
void merge(int v,int u)
{
	int t1,t2;
	t1=getf(v);
	t2=getf(u);
	// 将原来的靠左法则进行修改,利用两者树的深度来选定合并后的根节点。
    // 这里合并的时候判断树的深度,将其深度小的节点指向深度大的根节点
    if (rank[t1] < rank[t2]){
        parent[t1] = t2;	//t1的根节点的深度小,所以将t1的根节点的boss改成t2的根节点
    	}
    else if (rank[t1] > rank[t2]){
        parent[t2] = t1;	//如果t2的根节点的深度小,t2的根节点的boss改成t1的根节点
   	}
    else{ // rank[pRoot] == rank[qRoot] 相等的情况下,随便指向,但是高度需要维护
        parent[t2] = t1;	//深度相同时,优先使用靠左法则
        rank[t1] += 1; 		// 相当于t1根节点的深度增加了1
	}
}
// 判断两个节点是不是同一个集合的(不是必写项)
bool same(int x, int y) { 
        return find(x) == find(y);
    }


int main()
{
	int i,x,y;
	scanf("%d %d",&n,&m);	//读取人数和线索数
	init();					//初始化每个人的boss
	for(i=0;i<m;i++)
	{
		scanf("%d %d",&x,&y);
		merge(x,y);			//根据线索来合并犯罪团伙
	}


	//最后扫描有多少个独立的犯罪团伙
	for(i=0;i<n;i++)
	{
		if(f[i]==i)		//即找到几个老大
		sum++;
	}
	printf("%d\n",sum);
	return 0;
}

总结

  • 以上就是一个最基本的并查集模板。
  • 本篇只对并查集的做题步骤做一个总结,若想理解并查集工作原理,可参见下面参考文献中大佬们的博文。

参考文献

并查集—解密犯罪团伙
并查集详解 ——图文解说,简单易懂(转)
并查集,不就一并和一查?
夜深人静写算法(五)- 并查集
并查集——你一看就明白就会用
Union-Find 并查集算法详解
Union-Find 算法怎么应用?
数据结构与算法:学习并查集

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值