1. 程式人生 > 實用技巧 >[演算法筆記]並查集

[演算法筆記]並查集

並查集是一個非常優雅簡潔的,相對高階的資料結構,常常用於元素分組問題。

對於並查集的介紹和推導這裡不細說,推薦看Pecco的演算法學習筆記。這裡主要記錄我使用並查集刷題的模板和技巧。

一、什麼時候使用並查集?

個人認為並查集可以用在圖中,可以用來求取圖中的連通分量。當然題目不一定會直接給出圖的資料結構,可能是一個二維陣列(200. 島嶼數量),也可能是多個互相連線的結點(1319. 連通網路的操作次數)。可以多做幾題,體會一下。

二、模板:

class UnionFind {
private:
    vector<int> p, rank;

public:
    UnionFind(int n) {
        for (int i = 0; i < n; ++i) {
            p.push_back(i);
            rank.push_back(0);
        }
    }

    int find(int x) {
        return x == p[x] ? x : (p[x] = find(p[x]));
    }

    void unite(int x, int y) {
        x = find(x), y = find(y);
        if (rank[x] > rank[y]) {
            p[y] = x;
        } else {
            p[x] = y;
        }
        if (x != y && rank[x] == rank[y]) {
            rank[y]++;
        }
    }
};
  • 模板基本類似,只有構造有些許不同。視題目給出的“圖”結構的不同而定,這裡給出的模板給出的圖類似於鄰接表,以邊為儲存單位。如果題目給出的是一個二維陣列,那麼就類似於鄰接矩陣。可以按照下面的模板:
UnionFind(vector<vector<char>>& grid) {
    count = 0;
    int m = grid.size();
    int n = grid[0].size();
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            if (grid[i][j] == '1') {
                p.push_back(i * n + j);
                ++count;
            }
            else {
                p.push_back(-1);
            }
            rank.push_back(0);
        }
    }
}
  • 總結一下:並查集的p陣列一定儲存的是結點的父結點,也就是p陣列大小就等於題目中圖的結點數。

三、心得:

  1. ​ 同處於一個連通分量中的結點i的p[i]不一定相等,即使你使用 路徑壓縮 進行find操作。因為只有find操作可以將i結點從底向上更新p[i],但是之後可能進行union操作,這時就不能保證p[i]仍然是根結點的值,換句話說,此時根結點不一定就是父結點。

    ​ 因此,如果你想在程式中得到i結點的根結點不要使用p[i],請使用find(i),對它更新。

  2. ​ 如上所述,路徑壓縮較為重要,而按秩合併作用並不是很大,你也可以不使用:

    void unite(int x, int y) {
        x = find(x), y = find(y);
        p[x] = y;
    }