【笔记】
用有根树来表示集合,树中的每个结点都包含集合的一个成员,每棵树表示一个集合。
每个成员仅指向其父结点。每棵树的根包含了代表,并且是它自己的父结点。
MAKE-SET 创建一棵仅包含一个结点的树。
FIND-SET 找到树根。
UNION 使得一棵树根指向另一棵。
改进运行时间的启发式策略
按秩合并:使得包含较少结点的树的根指向包含较多结点的树的根。
路径压缩:使查找路径上的每个结点都直接指向根结点。路径压缩并不改变结点的秩。
代码
int p[maxn];
int rank[maxn];
void makeSet(int x) {
p[x] = x;
rank[x] = 0;
}
void link(int x, int y) {
if (rank[x] > rank[y]) { p[y] = x; }
else {
p[x] = y;
if (rank[x] == rank[y]) { rank[y] += 1; }
}
}
int findSet(int x) {
if (x != p[x]) { p[x] = findSet(p[x]); }
return p[x];
}
void unionSet(int x, int y) {
link(findSet(x), findSet(y));
}
void setInit(int n) {
for (int i=0;i<=n;i++) makeSet(i);
}
启发式策略对运行时间的影响
按秩合并 O(mlgn)
路径压缩 Θ(n+f·(1+log_{2+f/n}n))
同时 O(mα(n)),其中α(n)是一个增长及其缓慢的函数。
【练习】
21.3-1 按秩合并和路径压缩启发式的不相交集合森林重做里练习21.2-2。
setInit(16);
for (int i=1;i<=15;i+=2) unionSet(i,i+1);
for (int i=1;i<=13;i+=4) unionSet(i,i+2);
unionSet(1,5);
unionSet(11,13);
unionSet(1,10);
cout<<findSet(2)<<endl;
cout<<findSet(9)<<endl;
16
16
21.3-2 写出FIND-SET路径压缩的非递归版本。
int findSet2(int x) {
int y = x, z;
while (x != p[x]) { x = p[x]; }
while (y != p[y]) {
z = y;
y = p[y];
p[z] = x;
}
return x;
}
21.3-3 请给出一个包含m个MAKE-SET、UNION和FIND-SET操作的序列(其中n个是MAKE-SET操作),使得采用按秩合并时,这一操作序列的代价为Ω(mlgn)。
*21.3-4 证明:在采用了按秩合并和路径压缩时,任意一个包含m个MAKE-SET、FIND-SET和LINK操作的序列需要O(m)的时间。如果仅用启发式呢。