用处
判断图中是否有环,判断是否在一个集合中,找祖先节点,合并集合,判断两节点是否有关系
思想
任意遍历图中的一条边的两个顶点
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;
}