「程式碼隨想錄演算法訓練營」第四十七天 | 圖論 part5

云雀AC了一整天發表於2024-08-26

目錄
  • 並查集模板
  • 107. 尋找存在的路徑

並查集模板

原理:

並查集主要有兩個功能:

  • 將兩個元素新增到一個集合中。
  • 判斷兩個元素在不在同一個集合。

模板程式碼:

int n = 1005; // n根據題目中節點數量而定,一般比節點數量大一點就好
vector<int> father = vector<int> (n, 0); // C++裡的一種陣列結構

// 並查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}
// 並查集裡尋根的過程
int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]); // 路徑壓縮
}

// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 將v->u 這條邊加入並查集
void join(int u, int v) {
    u = find(u); // 尋找u的根
    v = find(v); // 尋找v的根
    if (u == v) return ; // 如果發現根相同,則說明在一個集合,不用兩個節點相連直接返回
    father[v] = u;
}

功能:

  1. 尋找根節點,函式:find(int u),也就是判斷這個節點的祖先節點是哪個。
  2. 將兩個節點接入到同一個集合,函式:join(int u, int v),將兩個節點連在同一個根節點上。
  3. 判斷兩個節點是否在同一個集合,函式:isSame(int u, int v),就是判斷兩個節點是不是同一個根節點。

按秩(rank)合併:

int n = 1005; // n根據題目中節點數量而定,一般比節點數量大一點就好
vector<int> father = vector<int> (n, 0); // C++裡的一種陣列結構
vector<int> rank = vector<int> (n, 1); // 初始每棵樹的高度都為1

// 並查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
        rank[i] = 1; // 也可以不寫
    }
}
// 並查集裡尋根的過程
int find(int u) {
    return u == father[u] ? u : find(father[u]);// 注意這裡不做路徑壓縮
}

// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 將v->u 這條邊加入並查集
void join(int u, int v) {
    u = find(u); // 尋找u的根
    v = find(v); // 尋找v的根

    if (rank[u] <= rank[v]) father[u] = v; // rank小的樹合入到rank大的樹
    else father[v] = u;

    if (rank[u] == rank[v] && u != v) rank[v]++; // 如果兩棵樹高度相同,則v的高度+1,因為上面 if (rank[u] <= rank[v]) father[u] = v; 注意是 <=
}

107. 尋找存在的路徑

題目連結:https://kamacoder.com/problempage.php?pid=1179
文章講解:https://programmercarl.com/kamacoder/0107.尋找存在的路徑.html
題目狀態:看題解

思路:

  1. 變數宣告

    • n:節點數量。
    • father:一個大小為 101 的向量,用於儲存每個節點的父節點。
  2. 並查集初始化

    • init():將每個節點的父節點初始化為自身。
  3. 尋根操作

    • find(int u):遞迴尋找節點 ( u ) 的根節點,並進行路徑壓縮以最佳化後續查詢。
  4. 判斷連通性

    • isSame(int u, int v):檢查兩個節點 ( u ) 和 ( v ) 是否屬於同一個連通分量。
  5. 合併操作

    • join(int u, int v):將節點 ( v ) 的根連線到節點 ( u ) 的根上,實現合併。
  6. 主函式

    • 讀取節點數 ( n ) 和邊數 ( m )。
    • 呼叫 init() 初始化並查集。
    • 讀取 ( m ) 條邊,每次呼叫 join(s, t) 將兩個節點連線。
    • 讀取 sourcedestination,判斷它們是否連通。
    • 輸出結果:如果連通,輸出 1;否則,輸出 0。

程式碼:

#include <iostream>
#include <vector>
using namespace std;

int n; // 節點數量
vector<int> father = vector<int> (101, 0); // 按照節點大小定義陣列大小

// 並查集初始化
void init() {
    for (int i = 1; i <= n; i++)  father[i] = i;
}
// 並查集裡尋根的過程
int find(int u) {
    return u == father[u] ? u : father[u] = find(father[u]);
}

// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 將v->u 這條邊加入並查集
void join(int u, int v) {
    u = find(u); // 尋找u的根
    v = find(v); // 尋找v的根
    if (u == v) return ; // 如果發現根相同,則說明在一個集合,不用兩個節點相連直接返回
    father[v] = u;
}

int main() {
    int m, s, t, source, destination;
    cin >> n >> m;
    init();
    while (m--) {
        cin >> s >> t;
        join(s, t);
    }
    cin >> source >> destination;
    if (isSame(source, destination)) cout << 1 << endl;
    else cout << 0 << endl;
}

相關文章