首页 > 代码库 > 笔试算法题(38):并查集(Union-Find Sets)

笔试算法题(38):并查集(Union-Find Sets)

出题:并查集(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 }