数据结构与算法之并查集

引言


并查集(Union-Find)是一种高效的数据结构,主要的操作有:

  • 合并(Union)
  • 查找(Find)
  • 路径压缩(可选)
    其中最基本的便是合并与查找

合并

为方便叙述,把所有元素视作点,元素之间的关系视作线,存在联系便存在关系(需要注意的是,这里的关系应当是1.自反的,2.对称的,3.传递的)

  • 自反:x与x存在关系
  • 对称:若x与y存在关系,则y与x也存在关系
  • 传递:若x与y存在关系,y与z存在关系,那么x与z也存在关系

所谓合并,便是将两个点之间“画”一条线。 又上边的定义不难理解相连的若干点之间互相存在关系,这样我们便可以吧相连的若干点看做一个**“等价类”“连通分支”**

合并主要的流程为:

  • 查找两个点的根节点(参见后边查找部分)
  • 判断两个点是否存在关系,即根节点是否相同
  • 若根节点相同,什么都不做;否则,合并两个点:将两个点所在的连通分支合并为一个连通分支,即将连通分支的根节点的前驱节点改为另一个连通分支的根节点
  • (上一步的优化版)若根节点相同,什么都不做;否则,合并两个点:将节点数较少的连通分支合并到节点数较多的连通分支上

查找

我们采用线性的数据结构:数组,来表示并查集:

  • count表示连通分支的个数,初始化为节点个数N
  • 数组front[]用来存储N个点的前驱节点,初始化为节点自己
  • 数组size[]用来表示连通分支包含的节点数,初始化为1(初始时每个节点都视为一个连通分支)
  • 每次合并count--

路径压缩

路径压缩即为,将所有节点的前驱节点改为连通分支的根节点
这样在寻找节点的根节点时耗时更少。


总结

使用路径压缩,和优化后的合并方法的并查集算法已经是理论上的最优算法。
附上LeetCode547题的题解:

class Solution {
    public int findCircleNum(int[][] M) {
        int N = M.length;	// 元素数量
        UF uf = new UF(N);
        // 遍历关系矩阵,如果i,j没有连接,连接他们
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (M[i][j] == 1) uf.union(i, j);
            }
        }
        // 得到朋友圈数量
        int count = uf.count();
        return count;
    }
}
// Union-Find Class
class UF {
    private int[] front;	// 存储前驱节点的位置
    private int[] size;		// 存储连通分支的大小
    private int count; 		// 连通分支的个数
    public UF(int N) {	// 初始化
        front = new int[N];
        size = new int[N];
        count = N;
        for (int i = 0; i < N; i++)
            front[i] = i;	// 指向自己
        for (int sz : size)
            sz = 1;	// 将每个节点视为一个连通分支
    }
    // 查找点p的根节点
    public int find(int p) {
        int temp = p;
        while(p != front[p]) p = front[p];
        // 路径压缩
        front[temp] = p;
        return p;
    }
    // 判断p,q是否连接
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }
    // 得到连通分支数
    public int count() {
        return count;
    }
    // 合并连通分支
    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) return;
        else {	// 优化版的合并
            if (size[pRoot] > size[qRoot]) {
                front[qRoot] = pRoot;
            } else {
                front[pRoot] = qRoot;
            }
        }
        // 合并后连通分支数减一
        count--;
    }  
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值