并查集
1 //-----并查集的初始化-----
2
3 //一开始有n个元素,互相独立,则构成了n个集合,每个集合的代表元素就是它本身
4
5 const int maxn = 100010;
6
7 int fa[maxn + 1]; //fa数组记录每个元素由谁代表
8 int sz[maxn + 1]; //sz数组记录每个集合的元素个数
9 int dep[maxn + 1]; //dep数组记录每个集合的树深度
10
11 inline void Initialize(int n){
12 for (int i = 1;i <= n;i++){ //一共有n个点
13 fa[i] = i; //把代表元素设置为自己
14 sz[i] = dep[i] = 1; //一开始的深度就是1,子树大小也是1,因为只有自己孤零零的一个元素
15 }
16 return;
17 }
18
19 //-----集合合并-----
20
21 int Findset(int x){ //这个是用来找代表元素的函数,递归找
22 if (fa[x] == x){
23 return x;
24 }
25 return Findset(fa[x]);
26 }
27
28 void Union(int x, int y){
29 int fx = Findset(x);
30 int fy = Findset(y);
31 if (fx == fy){ //如果发现是一家子的,那得了直接完事儿了
32 return;
33 }
34 fa[fx] = fy; //否则我们就搞个强扭的瓜,扭到一起去 反过来写也是可以
35 return;
36 }
37
38 //-----路径压缩-----
39
40 //大致思路就是在查询的操作过程中,把沿途经过的每一个节点的fa都设置为集合的代表元
41
42 int QuicklyFindset(int x){
43 if (x == fa[x]){ //如果就是代表元素就直接返回咯
44 return x;
45 }
46 fa[x] = QuicklyFindset(fa[x]); //在不是的情况下每一次都设置一遍
47 return fa[x];
48 }
49
50 /*
51 上述代码简写版:
52 int QuicklyFindset(int x){
53 return x == fa[x] ? x : (fa[x] == QuicklyFindset(fa[x]));
54 }
55 */
56
57 //-----启发式合并-----
58
59 //log的复杂度
60
61 //大体思路:在合并集合的过程中,我们尽量选择包含元素个数少的集合,将它合并到另一个集合之中去,使要改变代表元的元素尽可能的少
62 //这种将较小的集合合并到较大的集合之中的方法被称为 启发式合并,在其他的数据结构中也很常见
63
64 void HeuristicUnion(int x, int y){
65 int fx = Findset(x);
66 int fy = Findset(y);
67 if (fx == fy){
68 return;
69 }
70 if (sz[fx] > sz[fy]){
71 swap(fx, fy); //确定谁是骡子谁是马
72 }
73 fa[fx] = fy;
74 sz[fy] += sz[fx]; //子树的大小也要加起来
75 return;
76 }
77
78 //-----按深度合并-----
79
80 //log的复杂度
81
82 //大体思路:每一次合并的过程中,将深度较小的集合合并到深度较大的一方去,并更新一下新集合的深度
83 //值得一提的是,在路径压缩的时候,可能会破坏维护的深度值,但其实整体算法的复杂度不会变差
84
85 void DeepUnion(int x, int y){
86 int fx = Findset(x);
87 int fy = Findset(y);
88 if (fx == fy){
89 return;
90 }
91 if (dep[fx] > dep[fy]){
92 swap(fx, fy);
93 }
94 fa[fx] = fy;
95 if (dep[fx] == dep[fy]){ //只有两棵树深度相等的时候才会更新
96 dep[fy]++;
97 }
98 return;
99 }