上一篇实现kruskal算法用到了并查集,并大概自说自话地讲了并查集的原理。即只关心是否属于一个集合这个属性,不关心元素之间的关系(并查集看着有根树,肯定是连通且无环的),即不关心它们是怎么连接的。
通常
优化并查集的查询,用到了一种叫做路径压缩的技术。这个听起来很高端,其实没什么。如果觉得递归版本不好理解,就使用迭代版本。
优化并查集的合并,用到了一种叫做按秩合并的做法,用一个rank数组来记录代表元素子树的最大可能高度,把矮树直接挂到高树下作为其直接孩子。
以poj1611 http://poj.org/problem?id=1611 为例
借助POJ,我们还可以比较一下,只优化查询和优化查询+优化合并、迭代与递归的路径压缩三个版本的时间效率
#include <iostream> using namespace std; int const N = 40000; int par[N]; int cnt[N]; //POJ1611需要 //创建并查集 void makeSet(int n) { for (int i = 0; i != n; i++) { par[i] = i; cnt[i] = 1; } } //查询 //即查找集合的代表元素(查找树根) //路径压缩采用递归的方法(递归实现简洁、常见) //POJ1611 仅递归路径压缩优化 耗时 141MS int getPar(int i) { if (par[i] != i) { par[i] = getPar(par[i]); } return par[i]; } //_查询 //路径压缩采用迭代的方法 //POJ1611 仅迭代路径压缩优化 耗时 157MS int _getPar(int i) { //如果是直接孩子或树根,则直接返回树根 if (par[i] == i) return par[i]; int rec[N], root, k = 0; //退出循环时即找到了树根i,因为此时par[i] == i; //查找路径上的结点已经保存到rec数组,树根保存为root //数组中的元素其父亲全部设置为root for (; par[i] != i; i = par[i]) rec[k++] = i; root = i; for (i = 0; i != k; i++) par[rec[i]] = root; return root; } //简单合并 bool unionSet(int far, int son) { int a = getPar(far); int b = getPar(son); if (a == b) //判断是否属于同一个集合 return false; //合并失败:标志着它们属于同一个集合 par[b] = a; cnt[a] += cnt[b]; return true; //合并成功:标志着它们原本不属于同一个集合,现已经合并为同一个集合 } //_按秩合并 //这里秩是指每个结点的子树的最大可能高度(刚开始子树空,rank初始为0) //画画图就可以理解秩的含义了 //POJ1611 递归路径压缩优化+按秩合并优化 耗时 125MS int rank[N] = {0}; bool _unionSet(int x, int y) { int a = getPar(x); int b = getPar(y); if (a == b) //判断是否属于同一个集合 return false; if (rank[a] > rank[b]) { par[b] = a; //a为新树根 cnt[a] += cnt[b]; } else { par[a] = b; //b为新树根 cnt[b] += cnt[a]; if (rank[a] == rank[b]) rank[b]++; //b的子树的秩加1 } return true; } int main() { int n, m; while (cin >> n >> m, n||m) { makeSet(n); for (int i = 0; i != m; i++) { int t, first, cur; cin >> t >> first; for (int j = 1; j != t; j++) { cin >> cur; _unionSet(first, cur); } } cout << cnt[getPar(0)] << endl; } return 0; }
实际情况下,有路径压缩的并查集已经足够高效了,不需要按秩合并。
另外,并查集的题目往往涉及大量数据的读入,一般用scanf()更快。
最优版本改成scanf(),耗时则从125MS降到16MS。
据说还有一种方法,就是继续用cin cout,但在主函数内第一行加入一行代码:
ios::sync_with_stdio(NULL);