BFS和DFS詳解以及java實現


前言

圖在算法世界中的重要地位是不言而喻的,曾經看到一篇Google的工程師寫的一篇《Get that job at Google!》文章中說到面試官問的問題中幾乎有一半的問題都可以用圖的方法去解決。由此也可以看出圖確實適用范圍確實很廣。

圖的表示

閑話不多說,首先要介紹的就是圖的表示,圖最常用的兩種表示方法是鄰接表和鄰接矩陣。顧名思義,這兩種辦法分別用表和矩陣的方式描述圖中各頂點之間的聯系

下圖展示了兩種表示上面這個圖的方法

 

BFS

本文將着重介紹遍歷圖的兩種最常用的方法,分別為廣度優先遍歷和深度優先遍歷,后面會具體介紹為什么這么命名。首先來看廣度優先遍歷BFS(Breadth First Search),其主要思想是從起始點開始,將其鄰近的所有頂點都加到一個隊列(FIFO)中去,然后標記下這些頂點離起始頂點的距離為1.最后將起始頂點標記為已訪問,今后就不會再訪問。然后再從隊列中取出最先進隊的頂點A,也取出其周邊鄰近節點,加入隊列末尾,將這些頂點的距離相對A再加1,最后離開這個頂點A。依次下去,直到隊列為空為止。從上面描述的過程我們知道每個頂點被訪問的次數最多一次(已訪問的節點不會再訪問),而對於連通圖來說,每個頂點都會被訪問。加上每個頂點的鄰接鏈表都會被遍歷,因此BFS的時間復雜度是Θ(V+E),其中V是頂點個數,E是邊數,也就是所有鄰接表中的元素個數。為了更好的說明這個過程,下圖列出了對一個圖的BFS的過程

 

private static void bfs(HashMap<Character, LinkedList<Character>> graph,HashMap<Character, Integer> dist,char start)
{
    Queue<Character> q=new LinkedList<>();
    q.add(start);//將s作為起始頂點加入隊列
    dist.put(start, 0);
    int i=0;
    while(!q.isEmpty())
    {
        char top=q.poll();//取出隊首元素
        i++;
        System.out.println("The "+i+"th element:"+top+" Distance from s is:"+dist.get(top));
        int d=dist.get(top)+1;//得出其周邊還未被訪問的節點的距離
        for (Character c : graph.get(top)) {
            if(!dist.containsKey(c))//如果dist中還沒有該元素說明還沒有被訪問
            {
                dist.put(c, d);
                q.add(c);
            }
        }
    }
}

運行結果:

從運行結果我們也可以看到,w r作為距離為1的頂點先被訪問,x t v其后,最后訪問y u。上面的代碼使用了一個小的trick,用dist這個hash表來記錄每個頂點離s的距離,如果dist中沒有這個元素則說明還未被訪問,這時將距離寫入dist中。BFS訪問得到的每個節點與起始頂點的距離是起始頂點到達該頂點的最短距離。從感性認識上來說,BFS向外擴散的方式得到的距離就是最短距離。詳細的證明過程請參考CLRS上的相應章節

DFS

DFS(Depth First Search)深度優先搜索是從起始頂點開始,遞歸訪問其所有鄰近節點,比如A節點是其第一個鄰近節點,而B節點又是A的一個鄰近節點,則DFS訪問A節點后再訪問B節點,如果B節點有未訪問的鄰近節點的話將繼續訪問其鄰近節點,否則繼續訪問A的未訪問鄰近節點,當所有從A節點出去的路徑都訪問完之后,繼續遞歸訪問除A以外未被訪問的鄰近節點。因為是遞歸過程,所以我們用過程圖看一下也許會更直觀一些。

如下是DFS的代碼及運行結果

private static void dfs(HashMap<Character , LinkedList<Character>> graph,HashMap<Character, Boolean> visited)
{
    visit(graph, visited, 'u');//為了和圖中的順序一樣,我認為控制了DFS先訪問u節點
    visit(graph,visited,'w');
}
private static void visit(HashMap<Character , LinkedList<Character>> graph,HashMap<Character, Boolean> visited,char start)
{
    if(!visited.containsKey(start))
    {
        count++;
        System.out.println("The time into element "+start+":"+count);//記錄進入該節點的時間
        visited.put(start, true);
        for (char c : graph.get(start)) 
        {
        if(!visited.containsKey(c))
        {
            visit(graph,visited,c);//遞歸訪問其鄰近節點
        }
        }
        count++;
        System.out.println("The time out element "+start+":"+count);//記錄離開該節點的時間
    }
}

運行結果:

我們通過一個全局變量count記錄了進入每個節點和離開每個節點的時間,我們也可以看到進出元素的時間和過程圖中的訪問過程是一樣的。

總結

總的來說,BFS多用於尋找最短路徑的問題,DFS多用於快速發現底部節點。以后若有時間再貼幾道相關的題目上來。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM