2021年hznu寒假集训第二天 并查集

2021年hznu寒假集训第二天

关键词:代表元
解决问题:问题的朴素解法
概念:并查集主要记录节点之间的链接关系,而没有其他的具体的信息,仅仅代表某个节点与其父节点之间存在联系,它多用来判断图的连通性,如下图所示,这是一个并查集,其中箭头表示父子关系。
2021年hznu寒假集训第二天 并查集

优化

路径压缩

我们通常把路径优化后的结果叫做菊花图.
即将上面的图变成下面的
2021年hznu寒假集训第二天 并查集
接下来我们来介绍一下路径压缩.
C的父节点是B,而B的父节点是A,所以我们就可以把A设为C的父节点。
接下来看一下代码:

int find(int x)
{
	return x==parent[x]?parent[x]:parent[x]=find(parent[x]);
}

合并

2021年hznu寒假集训第二天 并查集
比如这题,我们想要证明环存在,进行完路径压缩,我们要合并。
合并就是将两个集变成一个集
2021年hznu寒假集训第二天 并查集

如图所示,0、2的父节点是1, 3的父节点是4,要想这两个集有关联,我们要尝试将他们的父节点相关联,例如这样
2021年hznu寒假集训第二天 并查集
那这个过程我们就把他叫做合并。
下面是它的代码:

void Union(int x,int y){
	int fx=find(x),fy=find(y);
	//找出父节点
	if(fx!=fy)
	//证明父节点不相同,即他们是在两个集合里面
		parent(fx)=fy;
		//则将其中一个父节点y设为另一个父节点x的父节点
}

下面我们来解决一下上面证明环存在的问题

#include<stdio.h>
#include<stdlib.h>

#define VERTICES 6
void initialise(int parent[]){
	int i;
	for(i=0;i<VERTICES;i++){
		parent[i]=-1;
	}
}
int find_root(int x,int parent[]){
	int x_root=x;
	while(parent[x_root]!=-1){
		x_root=parent[x_root];
	}
	return x_root;
}
/*1- union successfully,0-failed*/
int union_vertices(int x,int y,int parent[]){
	int x_root=find_root(x,parent);
	int y_root=find_root(y,parent);
	if(x_root==y_root){
		return 0;
	}
	else{
		parent[x_root]=y_root;
		return 1;
	}
}
int main(){
	int parent[VERTICES]={0};
	int edges[6][2]={
		{0,1},{1,2},{1,3},
		{2,4},{3,4},{2,5}
	};
	initialise(parent);
	int i;
	for(i=0;i<6;i++){
		int x=edge[i][0];
		int y=edge[i][1];
		if(union_vertices(x,y,parent)==0){
			printf("Cycle detected!\n");
			exit(0);
		}
	}
	printf("NO cycles found.\n");
	return 0;
}

但是这串代码有一个缺陷,如果我们的图是
比如第一次union(0,1),第二次(1,2),第三次(2,3)……
这个时候,会形成一个特别长的链,在查找parent的时候就会执行特别多次,时间复杂度是log n级别的
所以我们这个时候要再进行优化。

按秩合并

首先我们要引入秩的概念。
rank:指代树的高度
2021年hznu寒假集训第二天 并查集
这棵树x的高度是3;
2021年hznu寒假集训第二天 并查集
而这棵树y的高度则是1;
如果我们把树y接在树x上,即x作为y的parent,那么,他的rank并不发生改变;
反过来,他的rank变成了4;
后者显然是我们不希望的,所以我们要按秩合并。

if rank[x]>rank[y]
	parent[y]=x;
	//rank 不变->x
else
	parent[x]=y;

之前那道题的代码就可以优化了

#include<stdio.h>
#include<stdlib.h>

#define VERTICES 6
void initialise(int parent[],int rank[]){
	int i;
	for(i=0;i<VERTICES;i++){
		parent[i]=-1;
		rank[i]=0;
	}
}
int find_root(int x,int parent[]){
	int x_root=x;
	while(parent[x_root]!=-1){
		x_root=parent[x_root];
	}
	return x_root;
}
/*1- union successfully,0-failed*/
int union_vertices(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 0;
	}
	else{
		//parent[x_root]=y_root;
		if(rank[x_root]>rank[y_root]){
			parent[y_root]=x_root;
		}
		else{
			parent[x_root]=y_root;
		}
		return 1;
	}
}
int main(){
	int parent[VERTICES]={0};
	int rank[VERTICES]={0};
	int edges[6][2]={
		{0,1},{1,2},{1,3},
		{2,4},{3,4},{2,5}
	};
	initialise(parent,rank);
	int i;
	for(i=0;i<6;i++){
		int x=edge[i][0];
		int y=edge[i][1];
		if(union_vertices(x,y,parent,rank)==0){
			printf("Cycle detected!\n");
			exit(0);
		}
	}
	printf("NO cycles found.\n");
	return 0;
}

应用

最小生成树的构成

维护无向图连通性

拓展

带权并查集

种族并查集

上一篇:HZNU训练补题记录


下一篇:2021年hznu寒假集训第一天