每天一道算法題(六)深度優先算法(DFS)學習及Java實現

深度優先算法

定義

wiki上的解釋:

深度優先搜索算法(英語:Depth-First-Search,簡稱DFS)是一種用於遍歷或搜索樹或圖的算法。沿着樹的深度遍歷樹的節點,儘可能深的搜索樹的分支。當節點v的所在邊都己被探尋過,搜索將回溯到發現節點v的那條邊的起始節點。這一過程一直進行到已發現從源節點可達的所有節點爲止。如果還存在未被發現的節點,則選擇其中一個作爲源節點並重復以上過程,整個進程反覆進行直到所有節點都被訪問爲止。屬於盲目搜索。

當然看到上面這句話的時候,我並沒有理解什麼到底是DFS,因此又看了很多人的說話,有了下面一段話:
DFS的思想是從一個頂點V0開始,沿着一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然後從另一條路開始走到底。
DFS適合此類題目:給定初始狀態跟目標狀態,要求判斷從初始狀態到目標狀態是否有解。


深度與廣度的比較

我們搜索一個圖是按照樹的層次來搜索的。

我們假設一個節點衍生出來的相鄰節點平均的個數是N個,那麼當起點開始搜索的時候,隊列有一個節點,當起點拿出來後,把它相鄰的節點放進去,那麼隊列就有N個節點,當下一層的搜索中再加入元素到隊列的時候,節點數達到了N2,你可以想想,一旦N是一個比較大的數的時候,這個樹的層次又比較深,那這個隊列就得需要很大的內存空間了。

於是廣度優先搜索的缺點出來了:在樹的層次較深&子節點數較多的情況下,消耗內存十分嚴重。廣度優先搜索適用於節點的子節點數量不多,並且樹的層次不會太深的情況。

那麼深度優先就可以克服這個缺點,因爲每次搜的過程,每一層只需維護一個節點。但回過頭想想,廣度優先能夠找到最短路徑,那深度優先能否找到呢?深度優先的方法是一條路走到黑,那顯然無法知道這條路是不是最短的,所以你還得繼續走別的路去判斷是否是最短路?

於是深度優先搜索的缺點也出來了:難以尋找最優解,僅僅只能尋找有解。其優點就是內存消耗小,克服了剛剛說的廣度優先搜索的缺點。

這裏寫圖片描述

數字爲搜索順序

代碼(轉)

public class DFSTest {

        // 存儲節點信息
        private char[] vertices;

        // 存儲邊信息(鄰接矩陣)
        private  int[][] arcs;

        // 圖的節點數
        private int vexnum;

        // 記錄節點是否已被遍歷
        private boolean[] visited;

        // 初始化
        public DFSTest(int n) {
              vexnum = n;
              vertices = new char[n];
              arcs = new int[n][n];
              visited = new boolean[n];
              for (int i = 0; i < vexnum; i++) {
                 for (int j = 0; j < vexnum; j++) {
                     arcs[i][j] = 0;
                 }
              }
        }

        // 添加邊(無向圖)
        public void addEdge(int i, int j) {
              // 邊的頭尾不能爲同一節點
              if (i == j)return;

              arcs[i][j] = 1;
              arcs[j][i] = 1;
        }

        // 設置節點集
        public void setVertices(char[] vertices) {
            this.vertices = vertices;
        }

        // 設置節點訪問標記
        public void setVisited(boolean[] visited) {
            this.visited = visited;
        }

        // 打印遍歷節點
        public void visit(int i){
            System.out.print(vertices[i] + " ");
        }

        // 從第i個節點開始深度優先遍歷
        private void traverse(int i){
            // 標記第i個節點已遍歷
            visited[i] = true;
            // 打印當前遍歷的節點
            visit(i);

            // 遍歷鄰接矩陣中第i個節點的直接聯通關係
            for(int j=0;j<vexnum;j++){
                // 目標節點與當前節點直接聯通,並且該節點還沒有被訪問,遞歸
                if(arcs[i][j]==1 && visited[j]==false){
                    traverse(j);
                }
            }
        }

        // 圖的深度優先遍歷(遞歸)
        public void DFSTraverse(){
            // 初始化節點遍歷標記
            for (int i = 0; i < vexnum; i++) {
                visited[i] = false;
            }

            // 從沒有被遍歷的節點開始深度遍歷
            for(int i=0;i<vexnum;i++){
                if(visited[i]==false){
                    // 若是連通圖,只會執行一次
                    traverse(i);
                }
            }
        }

        // 圖的深度優先遍歷(非遞歸)
        public void DFSTraverse2(){
            // 初始化節點遍歷標記
            for (int i = 0; i < vexnum; i++) {
                visited[i] = false;
            }

            Stack<Integer> s = new Stack<Integer>();
            for(int i=0;i<vexnum;i++){
                if(!visited[i]){
                    //連通子圖起始節點
                    s.add(i);
                    do{ 
                        // 出棧
                        int curr = s.pop();

                        // 如果該節點還沒有被遍歷,則遍歷該節點並將子節點入棧
                        if(visited[curr]==false){
                            // 遍歷並打印
                            visit(curr);
                            visited[curr] = true;

                            // 沒遍歷的子節點入棧
                            for(int j=vexnum-1; j>=0 ; j-- ){
                                if(arcs[curr][j]==1 && visited[j]==false){
                                    s.add(j);
                                }
                            }
                        }
                    }while(!s.isEmpty());
                }
            }
        }

        public static void main(String[] args) {
            DFSTest g = new DFSTest(9);
            char[] vertices = {'A','B','C','D','E','F','G','H','I'};
            g.setVertices(vertices);

            g.addEdge(0, 1);
            g.addEdge(0, 5);
            g.addEdge(1, 0);
            g.addEdge(1, 2);
            g.addEdge(1, 6);
            g.addEdge(1, 8);
            g.addEdge(2, 1);
            g.addEdge(2, 3);
            g.addEdge(2, 8);
            g.addEdge(3, 2);
            g.addEdge(3, 4);
            g.addEdge(3, 6);
            g.addEdge(3, 7);
            g.addEdge(3, 8);
            g.addEdge(4, 3);
            g.addEdge(4, 5);
            g.addEdge(4, 7);
            g.addEdge(5, 0);
            g.addEdge(5, 4);
            g.addEdge(5, 6);
            g.addEdge(6, 1);
            g.addEdge(6, 3);
            g.addEdge(6, 5);
            g.addEdge(6, 7);
            g.addEdge(7, 3);
            g.addEdge(7, 4);
            g.addEdge(7, 6);
            g.addEdge(8, 1);
            g.addEdge(8, 2);
            g.addEdge(8, 3);

            System.out.print("深度優先遍歷(遞歸):");
            g.DFSTraverse();

            System.out.println();

            System.out.print("深度優先遍歷(非遞歸):");
            g.DFSTraverse2();
        }

}


----------
Output:
深度優先遍歷(遞歸):A B C D E F G H I 
深度優先遍歷(非遞歸):A B C D E F G H I 

博客中有些內容引自:
深度優先搜索(DFS)理解DFS概念上很清晰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章