并查集:(union-find sets) 是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskal算法求最小生成树。
并查集的精髓:
1、make_set(x) 把每一个元素初始化为一个集合
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、find_set(x) 查找一个元素所在的集合(有两种方法:朴素查找和采用路径压缩的方法查找,其中路径压缩有递归和非递归)
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
3、Union(x,y) 合并x,y所在的两个集合:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。
并查集的优化
1、find_set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次find_set(x)都是O(n)的复杂度,因此需要路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次find_set(x)时复杂度就变成O(1)了,路径压缩方便了以后的查找。
2、Union(x,y)时按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
const int size = 100;int father[size];int rank[size];//把每个元素初始化为一个集合 void makes_set(){ for(int i = 1; i <= n; i ++){ father[i] = i; rank[i] = 1; } return ;} //查找一个元素所在的集合,找到这个元素所在集合的祖先,这//是并查集判断和合并的最终依据。判断两个元素是否属于同一个集合,只要看它们所在集合的祖先是否相同 int find_set(int x){ if(x != father[x]){ father[x] = find_set(father[x]); } return father[x];}//非递归路径压缩 int find_set(int x){ int r = x; while(r != father[r]){ //查找根节点 r = father[r]; //找到根节点,用r记录 } int k = x; while(k != r){ //非递归路径压缩 j = father[k]; //用j暂存k的父节点 father[k] = r; //k指向根节点 k = j; //k移到父节点 } return r; //返回根节点的值 }//利用find_set找到两个集合的祖先,将一个集合的祖先指向另一个集合的祖先 void Union(int x, int y){ x = find_set(x); y = find_set(y); if(x == y){ return ; } if(rank[x] < rank[y]){ father[x] = y; } else{ if(rank[x] == rank[y]){ rank[x] ++; } else{ father[y] = x; } return ;}
来源:https://www.cnblogs.com/aiyelinglong/archive/2012/03/26/2418432.html