并查集

  • 参考《算法笔记》

目录

  • 并查集的定义
  • 井查集的基本操作
    • 初始化
    • 查找
    • 合并
    • 求解集合个数及每个集合的元素个数
  • 路径压缩

并查集的定义

  • 并查集是一种维护集合的数据结构,支持
    • 合并: 合并两个集合
    • 查找: 判断两个元素是否在一个集合

  • 并查集用一个数组实现:fahter[i] 表示元素 i i i 的父亲结点, 而父亲结点本身也是这个集合内的元素 ( 1 ≤ i ≤ N 1\leq i\leq N 1iN)。以这种父系关系来表示元素所属的集合。另外,如果 father[i] = i, 则说明元素 i i i 是该集合的根结点,但对同一个集合来说只存在一个根结点,且将其作为所属集合的标识
    并查集_第1张图片
int father[N];
father[1] = 1; 		// 1 的父亲结点是自己,也就是说 1 号是根结点
father[2] = 1;		// 2 的父亲结点是 1
father[3] = 2;		// 3 的父亲结点是 2
father[4] = 2; 		// 4 的父亲结点是 2
father[5] = 5; 		// 5 的父亲结点是自己, 也就是说 5 号是根结点
father[6] = 5; 		// 6 的父亲结点是 5

井查集的基本操作

  • 并查集的使用需要先初始化 father 数组, 然后再根据需要进行查找或合并的操作

初始化

  • 一开始, 每个元素都是独立的一个集合, 因此需要令所有 father[i] 等于 i
for (int i = 1; i <= N; i++) {
     
	father[i] = i; // 令 father[i] 为 -1 也可
}

查找

  • 由于规定同一个集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程
// 递推 (也可以用递归实现)
int findFather(int x) {
     
	while(x != father[x]) {
     
		x = father[x]; 
	}
	return x;	// 返回元素x所在集合的根结点
}

合并

  • 判断两个元素是否属于同一个集合, 只有当两个元素属于不同集合时才合并, 而合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点
void Union(int a, int b) {
     
	int faA = findFather(a);
	int faB = findFather(b);
	if (faA != faB) {
      // 如果不属于同一个集合,则合并它们
		father[faA] = faB;
	}
}

求解集合个数及每个集合的元素个数

  • 可以开一个 bool 型数组 flag[N] 来记录每个结点是否作为某个集合的根结点,这样当处理完输入数据之后就可以遍历所有元素,令它所在集合的根结点的 flag 值设为 true。最后累加 flag 数组中的元素即可得到集合数目
for(int i = 1; i <= n; i++) {
     
	isRoot[findFather(i)] = true;
}
  • 如果进一步要求每一个集合中元素的数目,则只需要把 isRoot 数组设置为 int 型,之后进行相应修改即可

路径压缩

  • 上面讲解的并查集查找函数是没有经过优化的, 在极端情况下效率较低。现在来考虑一种情况,即给出的元素数量很多并且形成一条链,那么这个查找函数的效率就会非常低
    • 如下图所示, 总共有 1 0 5 10^5 105 个元素形成一条链, 那么假设要进行 1 0 5 10^5 105 次查询, 且每次查询都查询最后面的结点的根结点, 那么每次都要花费 1 0 5 10^5 105 的计算量查找, 这显然无法承受
      在这里插入图片描述

优化查询操作

  • 由于 findFather 函数的目的就是查找根结点,因此可以在查询时把当前查询结点的路径上的所有结点的父亲都指向根结点, 查找的时候就不需要一直回溯去找父亲了, 查询的复杂度可产以降为 O ( 1 ) O(1) O(1)
    并查集_第2张图片
int findFather(int x) {
     
	int a = x;	// 由于x在下面的while中会变成根结点, 因此先把原先的x保存一下
	while (x != father[x]) {
      // 寻找根结点
		x = father[x];
	}
	// 到这里,x 存放的是根结点。下面把路径上的所有结点的 father 都改成根结点
	while(a != father[a]) {
     
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}
// 递归版本
int findFather(int v) {
     
	if (v  == father[v]) 
		return v; // 找到根结点
	else {
     
		int F = findFather(father[v]);
		father[v] = F;
		return F;
	}
}

你可能感兴趣的:(数据结构与算法)