1. 程式人生 > 其它 >無向圖判環+判連通性

無向圖判環+判連通性

理論連結

例題
\(HDU1272\) 小希的迷宮
\(AcWing\) \(4290\). 小希的迷宮

題目要求:判斷是不是連通無環的圖

總結
1、必須使用\(scanf\)!使用\(cin\)直接\(TLE\)!
2、無向圖判環,應儘量採用並查集(判斷連通性)+公式法(判環)
3、

方法1:數學公式法判環+並查集判連通性

判斷一個圖是否為連通圖且沒有環其實就一個條件,點數-1=邊數,但是需要特判(0,0)
此方法可以通過AcWing+HDU

方法2:並查集

  • 並查集判環,不用真的建圖
  • 因為不一定每個點都用上,所以需要記錄本輪使用了哪些節點
  • 檢查是否有環:
    判斷輸入邊的兩個點,如果它們已經在同一個家族內,說明有環
  • 無環情況下,還需要檢查是不是連通圖。
    這時,需要判斷家族數量是不是\(1\)
    此方法可以通過AcWing+HDU

方法3:\(DFS\)標記白灰黑

  • \(DFS\)判環,需要使用鄰接表建圖

  • 因為不一定每個點都用上,所以需要記錄本輪使用了哪些節點

  • 檢查是否有環:
    (1)列舉每個節點,找到一個使用過的節點進去開始深搜
    (2)需要單開一個狀態陣列\(vis\),來記錄哪些節點深搜過了

  • 在無環情況下,還需要判斷是不是連通
    列舉每個節點,如果出現過,但深搜沒有搜尋過,就意味著不連通
    此方法HDU可AC,AcWing會SE,原因可能是因為記憶體過大。

並查集+公式

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;

bool st[N];
bool flag;
int edges, points;
//最簡併查集
int p[N];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); //路徑壓縮
    return p[x];
}
//合併集合
bool join(int a, int b) {
    if (find(a) == find(b)) return false;
    p[find(a)] = find(b);
    return true;
}

//初始化並查集
void init() {
    for (int i = 0; i < N; i++) p[i] = i;
    //初始化狀態陣列
    memset(st, false, sizeof(st));
    flag = true;
    edges = points = 0;
}

int main() {
    int a, b;
    //初始化
    init();
    while (~scanf("%d%d", &a, &b), ~a && ~b) {
        if (a && b) {
            edges++; //增加一條邊
            if (!st[a]) {
                st[a] = true;
                points++;
            }
            if (!st[b]) {
                st[b] = true;
                points++;
            }
            if (!join(a, b)) flag = false;
        } else {
            //這個是應付AcWing的黑心資料 0 0 -1 -1而加的特判
            //如果連通,並且,無環
            flag && (edges == points - 1 || edges == 0) ? puts("Yes") : puts("No");
            init();
        }
    }
    return 0;
}

並查集+列舉

#include <bits/stdc++.h>
using namespace std;
const int N = 100500;

int n, m;         // n個頂點,m條邊
bool flag = true; //預設是合格的,如果有環是false,不連通也是false
//每組資料,是否符合要求:1、任意兩點間道路唯一,2、需要是連通的
bool st[N]; //節點是不是出現過

//最簡併查集
int p[N];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); //路徑壓縮
    return p[x];
}
//合併集合
bool join(int a, int b) {
    if (find(a) == find(b)) return false;
    p[find(a)] = find(b);
    return true;
}

// 初始化各種狀態陣列和標識資料
void init() {
    flag = true;
    //初始化並查集
    for (int i = 0; i < N; i++) p[i] = i;
    //清空狀態陣列
    memset(st, 0, sizeof st);
}

int main() {
    int a, b;
    //初始化
    init();
    while (~scanf("%d%d", &a, &b), ~a && ~b) {
        if (a && b) {               //一組資料輸入進行中
            st[a] = st[b] = true;   //標識a,b兩個點走過了
            if (!join(a, b))        //如果兩個點在一個集合中,說明有重複路線出現~
                flag = false;        //標記已經不合法
        } else {
            //單組資料輸入結束
            //如果沒有檢查到環,才有資格檢查是不是連通
            if (flag) {
                int cnt = 0;
                for (int i = 1; i < N; i++) {
                    if (st[i] && find(i) == i) cnt++; //判斷根節點cnt數目
                    if (cnt > 1) {
                        flag = false;
                        break;
                    }
                }
            }
            //檢查通過
            flag ? puts("Yes") : puts("No");

            //清空準備下一次輸入
            init();
        }
    }
    return 0;
}

dfs

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N << 1;
/*
7e5+10
=700000*4/1024/1024=2.67MB
這記憶體也不大啊,為啥會SE呢?不是說64MB的上限嗎?
被卡的好難受,這是逼我去用並查集+公式解題啊!
*/
//鄰接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool st[N];  //標識某個節點號是不是使用過
bool vis[N]; //是不是在dfs過程中搜索過
int flag;    // true:滿足兩個條件 false:不滿足兩個條件 (1)是不是有環,要求無環 (2)是不是連通,要求連通

// dfs無向圖判環
void dfs(int u, int fa) {               //從u節點開始搜尋,它的父節點是fa,輸入fa的意義在於不能走回頭路,防止死迴圈
    vis[u] = true;                      //標識u已經搜尋過了
    for (int i = h[u]; ~i; i = ne[i]) { //列舉u的每一條出邊
        int j = e[i];
        if (j == fa) continue; //放過來源節點
        if (vis[j]) {          //如果j被搜尋過,說明有環
            flag = false;      //標識有環
            return;
        }
        dfs(j, u); //繼續搜尋j節點,它的父節點是u
    }
}
//每組輸入的清空與初始化
void init() {
    memset(st, 0, sizeof(st));
    memset(vis, 0, sizeof(vis));
    memset(h, -1, sizeof h);
    idx = 0;
}

int main() {
    int a, b;
    init();
    while (scanf("%d%d", &a, &b) && ~a && ~b) {
        if (a && b) {                   //如果輸入的a,b是兩個非零數字
            st[a] = st[b] = true;       //標識a,b已使用
            add(a, b), add(b, a);       //新增兩條無向邊
        } else {                        //如果輸入的是兩個0,描述是一組資料輸入結束
            flag = true;                //預設是無環+連通圖
            // 1、檢查是不是無環
            for (int i = 1; i < N; i++) //列舉每個節點
                if (st[i]) {            //如果i節點使用過
                    //從找到的第一個使用過的節點開始進行dfs掃描
                    dfs(i, i);
                    break;
                }
            // 2、檢查連通性
            if (flag) { //掃描完的結果:1、找到了環,2、沒有找到環
                //下面將現次列舉每個使用過的點,檢視它是不是在dfs過程中被掃描到了,如果存在使過未掃到的點,則說明不連通
                for (int i = 1; i < N; i++)
                    if (st[i] && !vis[i]) { //如果存在出現過,但dfs沒有掃描到的點,說明不連通
                        flag = false;
                        break;
                    }
            }
            flag ? puts("Yes") : puts("No"); //兩個條件都滿足,輸出Yes,否則輸出No
            init();                          //因為要輸入下一組資料,需要清空狀態陣列
        }
    }
    return 0;
}