并查集算法

用处

判断图中是否有环,判断是否在一个集合中,找祖先节点,合并集合,判断两节点是否有关系
在这里插入图片描述

思想

任意遍历图中的一条边的两个顶点
1.如果两个顶点所在的树不是相同的根,就将两个顶点所在的集合合并
2.如果两个顶点所在的树的根相等,说明存在环,也即这两个顶点在同一集合中

集合表示方法:

perent[i]=j//表示第i个节点的父节点是j
注意:初始化parent[i]=-1;表示此节点无父节点,也即是此节点是根节点

并查集优化:

一.按秩合并

每一个秩其实就是树高,每次合并都用秩小的指向秩大的,可以保证树高最高为log2(n),使树更加平衡
当A集合层数大于B集合时:以A为父节点合并,合并后层数等于原A集合层数
当A集合层数小于B集合时:以B为父节点合并,合并后层数等于原B集合层数
当A几个层数等于B集合时:任意节点作为父节点合并,但此时合并后集合层数会加一

代码示例:
rank[i]=j:表示根节点为i的集合层数等于j

if(rank[x_root]>rank[y_root]){//将x作为根节点合并
		parent[y_root]=x_root;
}else{//将y作为根节点合并 
	rank[x_root]=y_root;
	if(rank[x_root]==rank[y_root]){//当秩相等时 
		rank[y_root]++;
	} 
} 

注意:按秩合并只能用在无向图判断是否在一组等情况,针对有向图比如求祖宗等题目按秩合并会改变答案

2.路径压缩

对于每一个节点,一旦向上走到了根节点,就把这个点到父亲节点的根直接改为指向根

不但是查询的节点,在查询过程中向上经过的所有的节点,都改为直接连到根上

代码示例:

int find(int x){//找根节点
	if(x==parent[x])return x;//返回根节点 
	return parent[x]=find(parent[x]);//路径压缩 
} 

演示优化过程(并查集模板)

#include<bits/stdc++.h>
using namespace std;
int parent[10005],rank[10005];
//找根节点 
int find(int x){
	if(x==parent[x])return x;//返回根节点 
	return parent[x]=find(parent[x]);//路径压缩 
}
//集合合并
void unionset(int x,int y){
	int x_root=find(x);
	int y_root=find(y);
	if(x_root==y_root)return;//在同一个集合中
	//按秩合并 
	if(rank[x_root]>rank[y_root]){
		parent[y_root]=x_root;	
	}
	else{
		parent[x_root]=y_root;
		if(rank[x_root]=rank[y_root]){
			rank[y_root]++;
		}
	}
}
//打印关系函数
void selectdots(){
	for(int i=1;i<=5;i++){
		printf("%d的根节点=%d\n",i,parent[i]);
	}
}

int main(){
	//初始化 
	for(int i=0;i<100;i++){
		parent[i]=i;
		rank[i]=0;
	}
	//初始化两个集合 A{1 ←2 ←3}  B{4 ←5} ;
	int con[3][2]={{2,1},{3,2},{5,4}};
	for(int i=0;i<3;i++){
		unionset(con[i][0],con[i][1]);//建立集合 
	} 
	printf("A{1 ←2 ←3}  B{4 ←5}两个集合没有合并:\n"); 
	selectdots();
	printf("2和4点是否有关系:");
	if(find(2)==find(4)){
		cout<<true<<endl;;
	}else{
		cout<<false<<endl;
	}
	
	printf("\n\n增加3 ←5关系:\n");
	unionset(5,3);
	selectdots();
	printf("2和4点是否有关系:");
	if(find(2)==find(4)){
		cout<<true<<endl;;
	}else{
		cout<<false<<endl;
	}
	
	printf("\n\n查询一次5的根节点:\n");
	find(5);
	selectdots(); 
	
	return 0;
}

在这里插入图片描述


并查集模板题:

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int parent[100005];
int rank[1000015];
int N,M;
//找根节点 
int find_root(int x,int parent[]){
	int x_root=x;
	while(parent[x_root]!=-1){
		x_root=parent[x_root];
	}
	return x_root;
}
//合并集合 
void unionset(int x,int y,int parent[],int rank[]){
	int x_root=find_root(x,parent);
	int y_root=find_root(y,parent);
	if(x_root==y_root)return;
	if(rank[x_root]>rank[y_root]){
		parent[y_root]=x_root;
	}else if(rank[x_root]<rank[y_root]){
		parent[x_root]=y_root;
	}else{
		parent[x_root]=y_root;
		rank[y_root]++;
	}
}

int main(){
	cin>>N>>M;
	memset(parent,-1,sizeof(parent));
	memset(rank,0,sizeof(rank));
	for(int i=0;i<M;i++){
		int z,x,y;
		scanf("%d %d %d",&z,&x,&y);
		if(z==1){//合并集合 
			unionset(x,y,parent,rank); 
		}else{//判断是否在同一集合 
			int x_root=find_root(x,parent);
			int y_root=find_root(y,parent);
			if(x_root==y_root)printf("Y\n");
			else printf("N\n");
		}
	}
	return 0;
}

优化代码:

示例题目:
在这里插入图片描述代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,p,parent[10005],rank[10005];
int find(int x){//找祖先,压缩路径
	if(x==parent[x])return x;//自己的爸爸是自己,自己就是祖宗 
	return parent[x]=find(parent[x]);//找爸爸
} 
int unite(int x,int y){
	int x_root=find(x);
	int y_root=find(y);
	if(rank[x_root]>rank[y_root]){//按秩合并
		parent[y_root]=x_root;
		
	}else{
		parent[x_root]=y_root;
		if(rank[x_root]==rank[y_root])rank[y_root]++;
	}
} 
int main(){
	cin>>n>>m>>p;
	//初始化
	for(int i=1;i<=n;i++){
		parent[i]=i;
		rank[i]=0;
	} 
	//建立集合 
	for(int i=0;i<m;i++){
		int x,y;
		cin>>x>>y;
		parent[find(x)]=find(y);//x点所在的集合和y点所在的集合合并 
	}
	//判断是否为亲戚 
	for(int i=0;i<p;i++){
		int tx,ty;
		cin>>tx>>ty;
		if(find(tx)==find(ty)){//判断祖宗是不是相同 
			printf("Yes\n");
		}else{
			printf("No\n");
		}
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值