并查集支持O(1)时间复杂度实现:
将两个集合合并。询问两个元素是否在一个集合中。基本原理:每个集合用一颗树来表示。树根的编号就是整个集合的编号。每个结点存储它的父结点,p[x]表示x的父结点。
问题1:如何判断树根:p[x] == x。
问题2:如何求x的集合编号:while (p[x] != x) x = p[x];。上述为朴素做法,可以通过路径压缩,进行优化。
问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号:p[px] = py。
2 模板 (1)朴素并查集: int p[N]; //存储每个点的祖宗节点 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) p[i] = i; // 合并a和b所在的两个集合: p[find(a)] = find(b); (2)维护size的并查集: int p[N], size[N]; //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; size[i] = 1; } // 合并a和b所在的两个集合: size[find(b)] += size[find(a)]; p[find(a)] = find(b); (3)维护到祖宗节点距离的并查集: int p[N], d[N]; //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; d[i] = 0; } // 合并a和b所在的两个集合: p[find(a)] = find(b); d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量 3 工程化 class UnionFind { public: UnionFind(int n) { this->n = n; p.resize(n); cnt.resize(n); d.resize(n); for (int i = 0; i < n; ++i) { p[i] = i; cnt[i] = 1; d[i] = 0; } } int find(int x) { if (x != p[x]) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } void merge(int x, int y) { int px = find(x), py = find(y); if (px != py) { p[px] = py; cnt[py] += cnt[px]; } return; } int size(int x) {//返回x所在集合的大小 return cnt[find(x)]; } private: int n; vector