1. 程式人生 > > Tarjan演算法求解一個無向圖中的割點和橋問題

Tarjan演算法求解一個無向圖中的割點和橋問題

基本概念

割點:Articulation Point
在無向連通圖中,刪除一個頂點v及其相連的邊後,原圖從一個連通分量變成了兩個或多個連通分量,則稱頂點v為割點,同時也稱關節點(Articulation Point)。
雙連通的圖:一個沒有關節點的連通圖稱為重連通圖(biconnected graph)(雙連通圖)。
連通度:k,若在連通圖上至少刪去k 個頂點才能破壞圖的連通性。

演算法應用

演算法應用:通訊網路中,用來衡量系統可靠性,連通度越高,可靠性越高。

求解方法

  1. 暴力求解,依次刪除每一個節點v,用DFS(或者BFS)判斷是否連通,再把節點加入圖中。若用鄰接表(adjacency list),需要做V次DFS,時間複雜度為O(V∗(V+E))O(V∗(V+E))
  2. Jarjan演算法,只用一次DFS求解。

第一個方法不多說:
對於第二個方法
我們要維持兩個資料結構:
dfn[u]:記錄節點u在DFS過程中被遍歷到的次序號。
low[u]:記錄節點u或u的子樹通過非父子邊追溯到最早的祖先節點(即DFS次序號最小)。
其中對於low[u]的理解是這樣的:當(u,v)為樹邊時,且v沒有被訪問過,則low[u]是min(low[u]和low[v])。當v被訪問過,如果v不是u的父節點(如果是則說明有重邊,不考慮),則(u,v)為回邊,則low[u]取min(low[u], dfn[v])。
這裡寫圖片描述
這裡寫圖片描述

程式碼思路解析

#include<iostream>
#include<vector>
#include<fstream>
using namespace std;
#define N 201
vector<int>G[N];
bool visit[N];
int dfn[N];
int low[N];
int parent[N];
int min(int a, int b)
{
    if (a < b) return a;
    else return b;
}
void input()
{
    int n, m;//分別表示頂點數和邊數
    ifstream in("input.txt");
    in >> n >> m;
    int a, b;
    for (int i = 1; i <= m; ++i)
    {
        in >> a >> b;
        G[a].push_back(b);/*鄰接表儲存無向邊*/
        G[b].push_back(a);
    }
}

void dfs(int u)
{
    //記錄dfs遍歷次序
    static int counter = 0;
    //記錄節點u的子樹數
    int children = 0;
    visit[u] = true;
    //初始化dfn與low
    dfn[u] = low[u] = ++counter;
    for (int j = 0; j < G[u].size(); j++)//遍歷與u相連的頂點
    {
        int v = G[u][j];
        if (!visit[v])
        {
            children++;
            parent[v] = u;//u是v的父節點
            dfs(v);//深度優先搜尋v
            low[u] = min(low[u], low[v]);//等v完成深度優先搜尋之後,low[u]記錄節點u或u的子樹通過非父子邊追溯到最早的祖先節點(即DFS次序號最小)
            if (parent[u] == -1 && children > 1)//對根節點u,若其有兩棵或兩棵以上的子樹,則該根結點u為割點;
            {
                cout << "1 articulation point: " << u<<endl;
            }
            if (parent[u] != -1 && low[v] >= dfn[u])//對非葉子節點u(非根節點),若其子樹的節點均沒有指向u的祖先節點的回邊(條件low[v] >= dfn[u]表達的就是),說明刪除u之後,根結點與u的子樹的節點不再連通;則節點u為割點。
            {
                cout << "2 articulation point: " << u << endl; //這樣做,可能出現某個頂點多次輸出,其實可以用一個指示變數來指示,做到頂點不重複輸出
            }
            if (low[v] >dfn[u]) //橋的條件
            {
                cout << "Bridge " << v << " " << u << endl;
            }

        }
        else if (v != parent[u]) {//節點v已訪問,則(u,v)為回邊,且不是重邊
            low[u] = min(low[u], dfn[v]);
        }
    }

}

int main()
{
    /*input();
    memset(dfn, -1, sizeof(dfn));
    memset(father, 0, sizeof(father));
    memset(low, -1, sizeof(low));
    memset(is_cut, false, sizeof(is_cut));
    count();*/
    memset(dfn, -1, sizeof(dfn));
    memset(low, -1, sizeof(low));
    memset(visit, false, sizeof(visit));
    memset(parent, -1, sizeof(parent));
    input();
    dfs(1);
    system("pause");
    return 0;
}

這裡寫圖片描述

參考