[演算法筆記]並查集
阿新 • • 發佈:2020-11-05
並查集是一個非常優雅簡潔的,相對高階的資料結構,常常用於元素分組問題。
對於並查集的介紹和推導這裡不細說,推薦看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陣列大小就等於題目中圖的結點數。
三、心得:
-
同處於一個連通分量中的結點i的p[i]不一定相等,即使你使用 路徑壓縮 進行find操作。因為只有find操作可以將i結點從底向上更新p[i],但是之後可能進行union操作,這時就不能保證p[i]仍然是根結點的值,換句話說,此時根結點不一定就是父結點。
因此,如果你想在程式中得到i結點的根結點,不要使用p[i],請使用find(i),對它更新。
-
如上所述,路徑壓縮較為重要,而按秩合併作用並不是很大,你也可以不使用:
void unite(int x, int y) { x = find(x), y = find(y); p[x] = y; }