IT技术博客大学习 共学习 共进步

算法模式:并查集

"地瓜哥"博客网 2026-06-03 09:03:23 累计浏览 4 次
本机暂存

在上一篇文章 算法模式:前缀树 介绍一种关于特殊的树的算法模式。本篇文章,再介绍一种关于特殊的树的算法模式:并查集。

并查集

并查集算法,英文是 Union-Find,是解决动态连通性(Dynamic Conectivity)问题的一种算法。动态连通性是计算机图论中的一种数据结构,动态维护图结构中相连信息。简单的说就是,图中各个节点之间是否相连、如何将两个节点连接,连接后还剩多少个连通分量。

动态连通性其实可以抽象成给一幅图连线。假设用一个数组表示一堆节点,每个节点都是一个连通分量。初始化视图如下:

并查集初始化
图 1. 并查集初始化

并查集的一个重要操作是 union(a, b),就是将节点 a 和节点 b 建立连接。如图所示:

并查集合并
图 2. 并查集合并

union(a, b) 还可以将已经建立的两个“子网”进行连接:

并查集再合并
图 3. 并查集再合并

并查集除了 union,还有一个重要操作是 connnected(a, b)。判断方法也很简单,从节点 ab 开始,向上查找,直到两个节点的根节点,判断两个根节点是否相等即可判断两个节点是否已经连接。为了加快这个判断速度,可以对其进行“路径压缩”,直白点说,就是将所有树的节点,都直接指向根节点,这样只需要一步即可到达根节点。路径压缩如图所示:

并查集路径压缩
图 4. 并查集路径压缩

简单代码实现如下:

package com.diguage.labs;

import java.util.ArrayList;
import java.util.List;

/**
 * 并查集
 *
 * PS:没想到代码竟然一次通过。
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2025-04-03 15:22:41
 */
public class UnionFind {
 /**
 * 连通分量
 */
 private int size;
 /**
 * 每个节点及对应的父节点
 */
 private int[] parent;

 public UnionFind(int size) {
 this.size = size;
 parent = new int[size];
 for (int i = 0; i < size; i++) {
 parent[i] = i;
 }
 }

 /**
 * a 和 b 建立连接
 */
 public void union(int a, int b) {
 int ap = find(a);
 int bp = find(b);
 if (ap == bp) {
 return;
 }
 parent[ap] = bp;
 size--;
 }

 /**
 * a 和 b 是否连通
 */
 public boolean connected(int a, int b) {
 int ap = find(a);
 int bp = find(b);
 return ap == bp;
 }

 /**
 * 连通分量
 */
 public int count() {
 return size;
 }

 /**
 * 查找节点 a 的根节点
 */
 private int find(int a) {
 int ap = parent[a];
 if (a != ap) {
 List<Integer> path = new ArrayList<>();
 path.add(a);
 // 向上查找根节点
 while (parent[ap] != ap) {
 path.add(ap);
 ap = parent[ap];
 }
 // 路径压缩
 // 只有一步,无需缩短路径
 if (path.size() == 1) {
 return ap;
 }
 for (Integer idx : path) {
 parent[idx] = ap;
 }
 }
 return ap;
 }

 public static void main(String[] args) {
 UnionFind uf = new UnionFind(10);
 uf.union(0, 1);
 uf.union(1, 2);
 uf.union(2, 3);
 uf.union(3, 4);
 uf.union(5, 6);
 uf.union(6, 7);
 uf.union(7, 8);
 uf.union(8, 9);
 uf.union(0, 5);
 System.out.println(uf.count() + ", " + uf.connected(0, 9));
 System.out.println(uf.count() + ", " + uf.connected(2, 9));
 System.out.println(uf.count() + ", " + uf.connected(3, 9));
 System.out.println(uf.count() + ", " + uf.connected(5, 9));
 }
}

建议继续学习

  1. 红黑树并没有我们想象的那么难(上) (累计阅读 21,401)
  2. 为什么算法这么难? (累计阅读 12,321)
  3. 浅谈MySQL索引背后的数据结构及算法 (累计阅读 11,482)
  4. 加州求职记 (累计阅读 11,440)
  5. 海量数据面试题举例 (累计阅读 10,983)
  6. 基于Redis构建系统的经验和教训 (累计阅读 10,441)
  7. 谷歌(Google)2011年校园招聘笔试题 (累计阅读 9,520)
  8. 浅谈redis数据库的键值设计 (累计阅读 9,281)
  9. 关于使用STL的红黑树map还是hashmap的问题 (累计阅读 8,802)
  10. 再谈“我是怎么招聘程序员的” (累计阅读 8,722)