数据结构与算法之并查集

引言


并查集(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--;
    }  
}

你可能感兴趣的:(算法,学习感悟)