赞
踩
并查集的概念:
并查集(Union-Find)是一种可以用来判断同属一个集合中相互关联的元素属于几个集合,也可以用来判断图结构中的两点是否是连通, 它也是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
并查集的算法介绍
联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:
并查集的应用
1,一般来说,可以用来合并集合元素,确定结合数量,查找元素处于哪个集合的位置。
2. 在图结构里,确定两个结点是否处于连通状态(解决动态连通性),尤其在图的应用中广泛涉及。
在解决动态连通性的场景里,我们需要思考两个问题,第一,给出的结点是否连通,第二,如果连通,则路径不需要记录,若不连通,则路径需要记录。
并查集的常见结构
分析并查集,建立模型
1)首先实现初始化,使对应的结点指向自己,形成独立的元素
void Init(int n) //初始化
{
int i;
fa[0] = MAXSIZE;//设置哨兵
for(i=1; i<=n; i++){
fa[i] = i;//让所有结点指向自己
}
}
2)然后实现查找操作(未进行路径压缩),查找祖先结点
例如:如果插入结点5必须查找到结点4,而根据以下代码需要从结点1开始,逐渐向右查找,直到找到4结点(祖先结点),返回结点4
int Find(int i)//查找 //未进行路径压缩
{
if(i==fa[i])//递归出口,当达到了祖先位置,就返回祖先
return i;
else
return Find(fa[i]);//不段向前查找祖先
}
但这样的算法在结点数量较大的时候,过于复杂,需要一个一个查找才能够找到祖先结点,为何不直接在每次查找的同时改变祖先结点,直接找到它呢?诶,这里就需要通过路径压缩的方式构造一种新的结构了。
但是哪种结构对于查找和修改的效率最高?毫无疑问是树,因此考虑如何将结点和族的关系以树的形式表现出来。
如图所示:
改进后的代码部分:
//进行路径压缩
int Find(int i)//查找
{
if(i==fa[i])//递归出口,当达到了祖先位置,就返回祖先
return i;
else{
fa[i] = Find(fa[i]);//不段向前查找祖先,并保存祖先
return fa[i];
}
}
当然,也可不用递归的方法,改用迭代也行
int Find(int i)//查找
{
while(fa[i] != i){//直到找到祖先结点
fa[i] = fa[fa[i]];
i = fa[i];
}
return i;
}
3)合并两结点,建立两个结点的父子关系
注:当 find_i==find_j 时,可以说明形成了连通路,不进行合并操作
void Union(int i, int j)//合并i, j
{
int find_i = Find(i);//找到i的祖先
int find_j = Find(j);//找到j的祖先
if(find_i==find_j)//导致形成连通路
return;
fa[find_i] = find_j; //i的祖先指向j的祖先
}
以下是完整代码:
#include<stdio.h> #define MAXSIZE 30000 int fa[MAXSIZE];//存储每一个元素的父结点 void Init(int n)//初始化 { int i; fa[0] = MAXSIZE;//设置哨兵 for(i=1; i<=n; i++){ fa[i] = i;//让所有结点指向自己 } } //未进行路径压缩 int Find(int i)//查找 { if(i==fa[i])//递归出口,当达到了祖先位置,就返回祖先 return i; else return Find(fa[i]);//不段向前查找祖先 } //进行路径压缩 int Find(int i)//查找 { if(i==fa[i])//递归出口,当达到了祖先位置,就返回祖先 return i; else{ fa[i] = Find(fa[i]);//不段向前查找祖先,并保存祖先 return fa[i]; } } //非递归方法 int Find(int i)//查找 { while(fa[i] != i){//直到找到祖先结点 fa[i] = fa[fa[i]]; i = fa[i]; } return i; } void Union(int i, int j)//合并i, j { int find_i = Find(i);//找到i的祖先 int find_j = Find(j);//找到j的祖先 if(find_i==find_j)//导致形成连通路 return; fa[find_i] = find_j; //i的祖先指向j的祖先 }
并查集的现实应用
当然现实生活中的情况比上述情况复杂多了,比如出现以下情况
还有这种情况
等等。
注意:这里的结构顺序是自下而上的,因为在并查集算法里,多个子结点可以指向一个祖先结点,但不可能一个子结点指向多个指向结点
所以,我们仍需要对算法进行更多的修改和精进,对于大数据类型,找到合适的数据结构,然后有针对性的进行改进,得到更高效的算法,例如Quick-Union算法及其多种改进算法,最终使得算法的复杂度降低到了近乎线性复杂度。
并查集的相关例题(leetcode)
int count; int *fa; void Init(int n) { count = n; fa = (int* )calloc(n, sizeof(int));//申请空间 for(int i=0; i<n; i++) fa[i] = i; } int Find(int i) { while(i != fa[i]){ fa[i] = fa[fa[i]]; i = fa[i]; } return i; } void Union(int i, int j)//合并i, j { int find_i = Find(i);//找到i的祖先 int find_j = Find(j);//找到j的祖先 if(find_i==find_j)//导致形成连通路 return; fa[find_i] = find_j; //i的祖先指向j的祖先 count--; } int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize){ Init(isConnectedSize);//初始化 for(int i=0; i<isConnectedSize; i++){//遍历全部结点 for(int j=i+1; j<isConnectedSize; j++){ if(isConnected[i][j] == 1){ Union(i, j); } } } return count; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。