判斷一個圖是否有環

對於無向圖html

算法1node

咱們知道對於環1-2-3-4-1,每一個節點的度都是2,基於此咱們有以下算法(這是相似於有向圖的拓撲排序):算法

  1. 求出圖中全部頂點的度,
  2. 刪除圖中全部度<=1的頂點以及與該頂點相關的邊,把與這些邊相關的頂點的度減一
  3. 若是還有度<=1的頂點重複步驟2
  4. 最後若是還存在未被刪除的頂點,則表示有環;不然沒有環

時間複雜度爲O(E+V),其中E、V分別爲圖中邊和頂點的數目,這個算法咱們稍後分析算法3的時候再分析。數組

 

算法2函數

深度優先遍歷該圖,若是在遍歷的過程當中,發現某個節點有一條邊指向已經訪問過的節點,而且這個已訪問過的節點不是當前節點的父節點(這裏的父節點表示dfs遍歷順序中的父節點),則表示存在環。可是咱們不能僅僅使用一個bool數組來標誌節點是否訪問過。以下圖spa

image

從節點1開始遍歷-接着遍歷2-接着遍歷3,而後發現3有一條邊指向遍歷過的1,則存在環。可是回到1節點時,它的另外一條邊指向已訪問過的3,又把這個環重複計算了一次。htm

咱們按照算法導論22.3節深度優先搜索中,對每一個節點分爲三種狀態,白、灰、黑。開始時全部節點都是白色,當開始訪問某個節點時該節點變爲灰色,當該節點的全部鄰接點都訪問完,該節點顏色變爲黑色。那麼咱們的算法則爲:若是遍歷的過程當中發現某個節點有一條邊指向顏色爲灰的節點,那麼存在環。則在上面的例子中,回溯到1節點時,雖然有一條邊指向已經訪問過的3,可是3已是黑色,因此環不會被重複計算。blog

下面的代碼中visit數組的值分爲0 1 2三種狀態分別表明白色、灰色、黑色,調用函數dfs能夠輸出圖中存在的全部環,圖用鄰接矩陣表示,若是兩個節點之間沒有邊則對應的值爲INT_MAX排序

void dfsVisit(vector<vector<int> >&graph, int node, vector<int>&visit,
               vector<int>&father)
{
    int n = graph.size();
    visit[node] = 1;
    //cout<<node<<"-\n";
    for(int i = 0; i < n; i++)
        if(i != node && graph[node][i] != INT_MAX)
        {
            if(visit[i] == 1 && i != father[node])//找到一個環
            {
                int tmp = node;
                cout<<"cycle: ";
                while(tmp != i)
                {
                    cout<<tmp<<"->";
                    tmp = father[tmp];
                }
                cout<<tmp<<endl;
            }
            else if(visit[i] == 0)
            {
                father[i] = node;
                dfsVisit(graph, i, visit, father);
            }
        }
    visit[node] = 2;
}

void dfs(vector<vector<int> >&graph)
{
    int n = graph.size();
    vector<int> visit(n, 0); //visit按照算法導論22.3節分爲三種狀態
    vector<int> father(n, -1);// father[i] 記錄遍歷過程當中i的父節點
    for(int i = 0; i < n; i++)
        if(visit[i] == 0)
            dfsVisit(graph, i, visit, father);
}

算法時間複雜度也是O(E+V)ip

 


對於有向圖

 

算法3

咱們都知道對於有向圖進行拓撲排序能夠判斷是否存在環。

對於有向圖的拓撲排序,你們都知道的kahn算法:

  1. 計算圖中全部點的入度,把入度爲0的點加入棧
  2. 若是棧非空:

    取出棧頂頂點a,輸出該頂點值,刪除該頂點

    從圖中刪除全部以a爲起始點的邊,若是刪除的邊的另外一個頂點入度爲0,則把它入棧

  3. 若是圖中還存在頂點,則表示圖中存在環;不然輸出的頂點就是一個拓撲排序序列

若是利用上面的拓撲排序算法求環,能夠判斷是否有環,可是輸出環時有點麻煩。由於並非全部最後剩餘的點都是環中的頂點,好比以下狀況:

image

對這個圖運行上面的算法,最後全部的節點都不會被刪除,可是隻有1 2 3是環中的點,4不是環中的節點。

對於上面的算法1,和算法3的思想是同樣的,因此也會存在這個問題。

 

算法4

其實算法2能夠原封不動的搬來就能夠檢測而且輸出全部有向圖中的環                                               本文地址

 

算法5

根據有向圖的強連通份量算法,每一個強連通份量中一定存在環,由於根據強連通份量的定義:從頂點 i 到 j 有一條路徑,而且從 j 到 i 也有一條路徑。求強連通份量的算法能夠參考維基百科here


補充:利用dfs來拓撲排序

 

只要對算法2稍微改動就能夠輸出有向圖的拓撲排序結果,即按照節點標記爲黑色的時間,越先標記爲黑色,在拓撲序列中越靠後。咱們在算法2的基礎上加了一個棧來保存拓撲排序的結果,只有dfsVisit的最後一行有改動,該算法,能夠完成拓撲排序,而且同時能夠檢測圖中是否有環。(該算法思想和算法導論22.4節拓撲排序同樣)

stack<int> tuopu;

void dfsVisit(vector<vector<int> >&graph, int node, vector<int>&visit,
               vector<int>&father)
{
    int n = graph.size();
    visit[node] = 1;
    //cout<<node<<"-\n";
    for(int i = 0; i < n; i++)
        if(i != node && graph[node][i] != INT_MAX)
        {
            if(visit[i] == 1 && i != father[node])//找到一個環
            {
                int tmp = node;
                cout<<"cycle: ";
                while(tmp != i)
                {
                    cout<<tmp<<"->";
                    tmp = father[tmp];
                }
                cout<<tmp<<endl;
            }
            else if(visit[i] == 0)
            {
                father[i] = node;
                dfsVisit(graph, i, visit, father);
            }
        }
    visit[node] = 2;
    tuopu.push(node);
}

void dfs(vector<vector<int> >&graph)
{
    int n = graph.size();
    vector<int> visit(n, 0); //visit按照算法導論22.3節分爲三種狀態
    vector<int> father(n, -1);// father[i] 記錄遍歷過程當中i的父節點
    for(int i = 0; i < n; i++)
        if(visit[i] == 0)
            dfsVisit(graph, i, visit, father);
}

 

【版權聲明】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3644225.html

相關文章
相關標籤/搜索