1. 程式人生 > 其它 >資料結構之圖的遍歷

資料結構之圖的遍歷

參考網址:https://www.jianshu.com/p/60eb50dbfc39

如果是遍歷一個數組,只需要從下標0到下標N-1迴圈就好了,遍歷一個連結串列只需要從頭指標開始直到沒有next為止,即使是遍歷一棵樹,也可以從根結點開始,按照前序、中序和後序等方式進行。之所以可以這樣,是因為這些結構都可以找到一個明確的起點,但圖不同。如下圖所示,有的人希望從A開始遍歷,有的人喜歡從C開始...,沒有辦法規定一個明確的起點。

如果沒有策略,遍歷一個圖就像走迷宮一樣,有可能在一個結點停留多次,也可能有幾個結點永遠不會訪問到。而圖的遍歷,通常有深度優先和廣度優先方式,接下來我們就看看這兩種方式是怎麼做的,有什麼區別。

深度優先遍歷

深度優先遍歷(Depth_First_Search)也稱為深度優先搜尋,簡稱為DFS。它是從圖中某個頂點v出發,訪問此頂點,然後從v的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。對於非連通圖,只需要對它的連通分量分別進行深度優先遍歷即可。接下來我們以一個示例演示圖的深度優先遍歷。如下圖所示:

圖的深度優先遍歷

在開始進行遍歷之前,我們還要準備一個數組,用來記錄已經訪問過的元素。其中0代表未訪問,1代表已訪問,如下所示:

visited陣列

為了便於演示,假設我們是在走迷宮,A是入口,每次都向右手邊前進。首先從A走到B,結果如下:

A-B

B之後有三個路,我們依然選擇最右邊,如此下去,直到走到F,如下所示:

B-F

到達F後,如果我們繼續按照向右走的原則,就會再次訪問A,此時我們訪問除了A後的最右側通道,也就是訪問G,如下所示:

F-G

到達G後,可以發現B和D都走過了,這時候走到H,如下所示:

G-H

到達H後,可以發現D和E都走過了,也就是說走到了盡頭,但是並不代表所有的頂點都訪問過了,因為除了最右側頂點,每個頂點還可能和更多的頂點連通,所以我們從H退回到G,發現全部走過了,再向前退回到F,也全部走過了,直到退回到B時,發現 I 還沒走過,於是訪問頂點 I,如下所示:

B-I

同理,訪問 I 之後,發現與 I 連通的頂點都訪問過了,所以再向前回退,直到回到頂點A,發現全部頂點都訪問過了,至此遍歷完畢。

廣度優先遍歷

深度優先遍歷可以認為是縱向遍歷圖,而廣度優先遍歷(Breadth_First_Search)則是橫向進行遍歷。還以上圖為例,不過為了方便檢視,我們把上圖調整為如下樣式:

廣度優先遍歷圖

我們依然以A為起點,把和A鄰接的B和F放在第二層,把和B、F鄰接的C、I、G、E放在第三層,剩下的放在第四層。廣度優先遍歷就是從上到下一層一層進行遍歷,這和樹的層序遍歷很像。我們依然藉助一個佇列來完成遍歷過程,因為和樹的層序遍歷很像,這裡只展示結果,如下所示:

廣度優先遍歷佇列

對於非連通圖,依然通過visited陣列來進行判斷即可。

程式碼實現

圖的儲存方式有很多種,但是用來實現遍歷的思路是一致的,我們以鄰接矩陣為例,給出DFS和BFS的參考實現。

深度優先遍歷實現

private void DFS(int i) {
    // 標記當前元素已經訪問
    visited[i] = true;
    System.out.println("當前訪問頂點:" + getVertexByIndex(i));

    int next = getFirstNeighbor(i);

    while (next != -1) {
        if (!visited[next]) {
            DFS(next);
        }
        next = getNextNeighbor(i, next);

    }
}

public void DFS() {
    for (int i = 0; i < vertexList.size(); i++) {
        visited[i] = false;
    }
    // 非連通圖,不同的連通分量要單獨進行DFS
    for (int i = 0; i < vertexList.size(); i++) {
        if (!visited[i]) {
            DFS(i);
        }
    }
}

廣度優先遍歷實現

private void BFS(int i) {
    // 標記當前元素已經訪問
    visited[i] = true;
    System.out.println("當前訪問頂點:" + getVertexByIndex(i));

    int cur, next;
    LinkedList<Integer> queue = new LinkedList<>();
    queue.addLast(i);
    while (!queue.isEmpty()) {
        cur = queue.removeFirst();
        next = getFirstNeighbor(cur);
        while (next != -1) {
            if (!visited[next]) {
                // 標記當前元素已經訪問
                visited[next] = true;
                System.out.println("當前訪問頂點:" + getVertexByIndex(next));
                queue.addLast(next);
            }
            next = getNextNeighbor(cur, next);
        }
    }
}

public void BFS() {
    for (int i = 0; i < vertexList.size(); i++) {
        visited[i] = false;
    }
    for (int i = 0; i < vertexList.size(); i++) {
        if (!visited[i]) {
            BFS(i);
        }
    }
}

完整程式碼已上傳至我的github



作者:大大紙飛機
連結:https://www.jianshu.com/p/60eb50dbfc39
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。