议题:并查集(Union-Find Sets)
分析:
-
一种树型数据结构,用于处理不相交集合(Disjoint Sets)的合并以及查询;一开始让所有元素独立成树,也就是只有根节点的树;然后根据需要将关联的元素(树)进行合并;合并的方式仅仅是将一棵树最原始的节点的父亲索引指向另一棵树;
-
优化:加入一个rank数组存储节点深度的下界(从当前节点到其最远子节点的距离),从而可以启发式的对树进行合并,从而减少树的深度,防止树的退化;使 得包含较少节点的树根指向包含较多节点的树根,具体指代为树的高度;另一个优化就是路径压缩,尽可能将子节点都直接连接到根节点之后;
-
并查集的空间复杂度为O(N),构建一个集合的时间复杂度为O(N);压缩后的查找复杂度是一个很小的常数;应用:Kruskal算法求最小生成树中判断新加入的边是否在同一棵树内部;两个节点的最近公共祖先(Least Common Ancestors);
-
初始化father:各个节点独立成树,并且其father[i]=i,也就是其父节点就是其自身;
father[i]
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
初始化rank:各个节点为根节点,所以高度都为1;
rank[i]
0 1 2 3 4 5 6 7 8 9
1 1 1 1 1 1 1 1 1 1
合并2和6:由于rank[2]=rank[6],所以将2的父亲索引指向6,这样2和6就在同一棵树;并需将6的rank值增加1;
father[i]
0 1 2 3 4 5 6 7 8 9
0 1 6 3 4 5 6 7 8 9
rank[i]
0 1 2 3 4 5 6 7 8 9
1 1 1 1 1 1 2 1 1 1
样例:
1 int *father; 2 int *rank; 3 4 /** 5 * 并查集的初始化: 6 * 数组father中的元素在最开始ide时候都是独立的树,也就是只有根节点 7 * 的树,数组father的下标i表示节点,而father[i]的值表示i节点的父亲 8 * 节点;rank[i]=1表示一开始所有树节点的高度都为1 9 * */ 10 void init(int cap) { 11 father=new int[cap]; 12 rank=new int[cap]; 13 /** 14 * 时间复杂度为O(N) 15 * */ 16 for(int i=0;i<10;i++) { 17 father[i]=i; 18 rank[i]=1; 19 } 20 } 21 22 void clean() { 23 delete [] father; 24 delete [] rank; 25 } 26 /** 27 * 查找元素所在的集合并进行路劲压缩: 28 * 由于需要频繁使用GetFather()函数,并且其时间复杂度受树结构影响; 29 * 当元素较多的时候,集合退化成链表,则GetFather()需要O(N),所以 30 * 需要对其进行优化,每次调用GetFather()的时候都将输入元素压缩成 31 * 最原始父亲节点的直接子节点 32 * */ 33 int GetFather(int son) { 34 if(father[son]==son) 35 return son; 36 else { 37 father[son]=GetFather(father[son]); 38 return father[son]; 39 } 40 } 41 42 /** 43 * 合并两个不相交的集合: 44 * 输入元素x和y来自两个不相交的集合,找到其最原始的父亲节点 45 * 并将一个原始父亲节点设置为另一个原始父亲节点的父亲节点 46 * */ 47 void Union1(int x, int y) { 48 /** 49 * GetFather()为递归寻找输入节点的最原始的父亲节点 50 * */ 51 int fx=GetFather(x); 52 int fy=GetFather(y); 53 /** 54 * 判断x和y是否来自同一棵树,如果不是才进行赋值;其实可以 55 * 不同进行判断(省去if语句) 56 * 注意最原始父亲节点的father[i]=i; 57 * */ 58 if(fx!=fy) 59 father[fx]=fy; 60 } 61 62 /** 63 * 利用rank加权数组启发式进行合并 64 * */ 65 void Union2(int x, int y) { 66 int fx=GetFather(x); 67 int fy=GetFather(y); 68 69 if(fx==fy) return; 70 /** 71 * rank[fx]较大,说明其越靠近根节点,则将 72 * fy连接到其后面可以压缩路径 73 * */ 74 if(rank[fx]>rank[fy]) 75 father[fy]=fx; 76 else { 77 if(rank[fx]==rank[fy]) 78 rank[fy]++; 79 father[fx]=fy; 80 } 81 } 82 83 /** 84 * 判断两个元素是否属于同一个集合: 85 * 利用GetFather()函数判断其最原始父亲节点是否相同 86 * */ 87 bool IsSameSet(int x, int y) { 88 return GetFather(x)==GetFather(y); 89 }