數據結構與算法複習(01)-並查集

本文首發於我的Github博客

本文是數據結構預算法複習系列的第一篇博文,會介紹寫作該系列博文的原因

本文複習了並查集的概念,基礎的API,良好的實現(路徑壓縮與權重),簡單的應用和變式

相關代碼

寫作本篇博文的原因

  1. 生活所迫
    1. 懂的都懂,IT行業從業者(或者是考研保研),跑不了的
  2. 數據結構與算法是大學生活裏花費的時間很多的部分,複習一下,也算是對自己的大學生涯有個交代
  3. 三次元的生活真的無聊,找點代碼寫,嘿嘿(難道不能寫工程項目嗎?質問!)

並查集的概念與基礎API

並查集的概念

假設擁有N個元素1…N,我們需要對其做兩種基礎的操作:

  1. 宣告某兩個元素a, b處在同一個等價類裏
  2. 判斷某兩個元素a, b是否在一個等價類裏

用於處理這種問題,提供這兩種操作接口的數據結構,可以被稱爲並查集

並查集基礎的API

interface UnionFind {
    void union(int a, int b);
    boolean isConnected(int a, int b);
}

良好的實現

良好的實現往往將一個等價類以的形式進行組織,使用一個數組int[] parentparent[i]記錄了元素i的父親節點

如下圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tY25IREj-1592185859107)(union-find-tree.png)]

  • isConnected(a, b)接口即判斷ab是否在同一棵樹內(是否有同樣的根)
  • union(a, b)接口即需要將a所屬的樹歸併到b所屬的樹下,或者將b所屬的樹歸併到a所屬的樹下
  • 提高執行速度,就是想辦法降低樹的高度

路徑壓縮

路徑壓縮基本上能夠完全解決並查集的接口執行速度問題

其基本思想是:

在沿着某條路徑查詢到a元素的根之後,將這條路徑上所有的元素父親,都直接設爲根節點
這樣,在以後查詢的時候,查找到這條路徑上的元素,只需要一次向上查詢,就可以找到根節點了

路徑壓縮實現
public class PathCompressUnionFindImpl implements UnionFind {
    int[] parent;

    public PathCompressUnionFindImpl(int n) {
        parent = new int[n];
        for (int i = 0; i < parent.length; ++i) {
            parent[i] = i;
        }
    }

    private int find(int a) {
        if (parent[a] == a) {
            return a;
        }
        return parent[a] = find(parent[a]);
    }

    @Override
    public void union(int a, int b) {
        int rootA = find(a), rootB = find(b);
        parent[rootA] = rootB;
    }

    @Override
    public boolean isConnected(int a, int b) {
        return find(a) == find(b);
    }
}

加權並查集

雖然路徑壓縮基本能夠解決並查集的執行速度問題,但是還有一個解決方案值得我們思考

加權並查集,其實是基於這樣的觀察:

在代表一個等價類的樹的高度快速增長時,往往是由於將高的樹合併成爲了矮的樹的子樹
如果每次合併,都將矮的樹合併成爲高的樹的子樹,那麼樹的高度增長就會減緩

從數學的角度來看,減緩的原因是

每次當樹的高度增加1,就意味着樹的節點總數至少翻倍了
那麼一共有N個節點,樹的高度最多也就是lg N

加權並查集實現
public class WeightedUnionFindImpl implements UnionFind {
    int[] parent, size;

    private int find(int a) {
        while (parent[a] != a) {
            a = parent[a];
        }
        return a;
    }

    public WeightedUnionFindImpl(int n) {
        parent = new int[n];
        size = new int[n];
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    @Override
    public void union(int a, int b) {
        int rootA = find(a), rootB = find(b);
        if (size[rootA] > size[rootB]) {
            parent[rootB] = rootA;
            size[rootA] += size[rootB];
        } else {
            parent[rootA] = rootB;
            size[rootB] += size[rootA];
        }
    }

    @Override
    public boolean isConnected(int a, int b) {
        return find(a) == find(b);
    }
}

並查集的簡單應用

貼兩道OJ題目,都是簡單練手的

洛谷P3367 模板題
洛谷UVA10583 簡單變式

並查集的簡單變式

等價類的個數

也就是樹的根節點的個數,一個節點a是根節點當且僅當parent[a] == a

int nClass() {
    int nClass = 0;
    for (int i = 0; i < parent.length; ++i) {
        if (parent[i] == i) {
            nClass++;
        }
    }
    return nClass;
}

可以維護的等價類性質

比如:

  • 給定元素a,求a所在等價類的元素個數
  • 給定元素a,求a所在等價類的最大值
  • 給定元素a,求a所在等價類的最小值

這類性質只需要新開一個數組,並在union時進行維護就可以了:

void union(int a, int b) {
    //....
    parent[rootA] = rootB;
    size[rootB] += size[rootA];
    maximum[rootB] = Math.max(maximum[rootA], maximum[rootB]);
    minimum[rootB] = Math.max(minimum[rootA], minimum[rootB]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章