#并查集
并查集(合并与查询)
假设有五个人,A,B,C,D,E。A和B是朋友,B和C是朋友,D和E是朋友,那么我们可以看到A-B-C这个朋友圈,D-E这个朋友圈,我们发现A,C虽然不是朋友但是他们通过B 相连,在同一个朋友圈内,所以我们查询的时候可以得到A,C属于同一个集合,而A,D不属于同一个集合。
朴素版
void initialize( int Arr[ ], int N) {
for(int i = 0;i<N;i++)
Arr[ i ] = i ;//初始化,每个元素的老大都是自己
}
bool find( int Arr[ ], int A, int B) {
if(Arr[ A ] == Arr[ B ])//A和B的老大相同
return true;
else
return false;
}
void union(int Arr[ ], int N, int A, int B)
{
int TEMP = Arr[ A ];
for(int i = 0; i < N;i++){
if(Arr[ i ] == TEMP)//把A、B所在的两个集合合并,
//所有和A有相同老大的转为拜B的老大为老大
Arr[ i ] = Arr[ B ];
}
}
这个就是朴素版并查集,我们发现每次合并集合都要遍历所有元素,时间复杂度很大,所以我们需要对其进行优化。
优化1:利用根节点
我们之前用Arr数组存的是元素之间的连通关系,现在我们用Arr表示父节点,即Arr[A]是A的父节点,一直追溯(找祖宗),直到找到Arr[x]=x,即x的父节点是自己,说明这个x就是根节点,所以我们判断A,B是否属于同一个祖宗(根节点),就可以知道他们是否有关系。
(初始化和之前一样)
int root(int Arr[ ],int i){
while(Arr[ i ] != i) {
i = Arr[ i ];
}
return i;//返回根节点
}
int union(int Arr[ ] ,int A ,int B){
int root_A = root(Arr, A);
int root_B = root(Arr, B);
Arr[ root_A ] = root_B ; //A的根节点改成B的根节点,表示将A,B连通
}
bool find(int A,int B){
if( root(A)==root(B) )
return true;
else
return false;
}
优化2:减小树的大小
如上面的代码,我们将每次都直接将A连接到B上,可能会导致最后得到的树很大很乱,而我们每次都将小的集合合并到大的集合上,这样就可以把合并、查找的时间复杂度做到更好。
void weighted-union(int Arr[ ],int size[ ],int A,int B){
int root_A = root(A);
int root_B = root(B);
if(size[root_A] < size[root_B ]){
Arr[ root_A ] = Arr[root_B];
size[root_B] += size[root_A];
}
else{
Arr[ root_B ] = Arr[root_A];
size[root_A] += size[root_B];
}
}
**优化3:路径亚索压缩 **
上面的方法我们会得到一个很多层的树,每次寻找根节点要循环或者递归寻找,如果数据大的话还是比较耗时的,那么我们可以在每次合并之后,只用一个点做父节点,其他的点全做子节点,即一个折扇一样,父节点好比扇子的转轴,子节点好像扇骨的末端。
int root(int x)
{
if(arr[x]!=x)
{
return x=arr[x]=root(arr[x]);
//这样就把x到根节点这一条路径上的所有点都直接与父节点相连
//方便以后查询
}
return x;
}
洛谷P3367(并查集模板)
这题对初学者有点不友好,因为需要用到路径压索才能通过,不然只有70分别问我怎么知道的 。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double EPS=1e-6;
typedef long long ll;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,m;
int arr[N];
int sizes[N];
int root(int x)
{
if(arr[x]!=x)
{
return x=arr[x]=root(arr[x]);
}
return x;
}
void uni(int x,int y)
{
int rootx=root(x);
int rooty=root(y);
if(sizes[rooty]>sizes[rootx])
{
arr[rootx]=rooty;
sizes[rooty]+=sizes[rootx];
}
else
{
arr[rooty]=rootx;
sizes[rootx]+=sizes[rooty];
}
}
void finds(int x,int y)
{
if(root(x)==root(y))
{
printf("Y\n");
}
else
{
printf("N\n");
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
arr[i]=i,sizes[i]=1;
for(int i=1;i<=m;i++)
{
int z,x,y;
scanf("%d%d%d",&z,&x,&y);
if(z==1)
{
uni(x,y);
}
else
{
finds(x,y);
}
}
}
大一萌新初学,讲述如有不当之处还望指出。