算法#17--無向圖的深度優先搜索和廣度優先搜索

無向圖

1.定義

在數學上,一個圖(Graph)是表示物件與物件之間的關係的方法,是圖論的基本研究對象。一個圖看起來是由一些小圓點(稱爲頂點或結點)和連結這些圓點的直線或曲線(稱爲邊)組成的。

如果給圖的每條邊規定一個方向,那麼得到的圖稱爲有向圖,其邊也稱爲有向邊。在有向圖中,與一個節點相關聯的邊有出邊和入邊之分,而與一個有向邊關聯的兩個點也有始點和終點之分。相反,邊沒有方向的圖稱爲無向圖

某個頂點的度數即爲連接它的邊的總數。路徑或者環的長度爲其中所包含的邊數。

2.代碼

我們可以抽象出以下的表示圖的API:

Graph的API的實現可以由多種不同的數據結構來表示,最基本的是維護一系列邊的集合,如下:

還可以使用鄰接矩陣來表示:

也可以使用鄰接列表來表示:

由於採用如上方式具有比較好的靈活性,採用鄰接列表來表示的話,可以定義如下數據結構來表示一個Graph對象。

點擊下載代碼


import java.util.ArrayList;
import java.util.List;

public class Graph {
    private int verticals;//頂點個數
    private int edges;//邊的個數
    private List<Integer>[] adjacency;//頂點聯接列表

    @SuppressWarnings("unchecked")
    public Graph(int vertical) {
        this.verticals = vertical;
        this.edges = 0;
        adjacency = (List<Integer>[])new List[vertical]; 
        for (int v = 0; v < vertical; v++) {
            adjacency[v]=new ArrayList<Integer>();
        }
    }

    public int GetVerticals () {
        return verticals;
    }

    public int GetEdges() {
        return edges;
    }

    public void AddEdge(int verticalStart, int verticalEnd) {
        adjacency[verticalStart].add(verticalEnd);
        adjacency[verticalEnd].add(verticalStart);
        edges++;
    }

    public List<Integer> GetAdjacency(int vetical) {
        return adjacency[vetical];
    }

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        List<Integer> list = G.GetAdjacency(4);
        for(int w : list) {
            System.out.print(w + " ");
        }
    }
}

輸出:

3 6 5 9 

採用以上三種表示方式的效率如下:

深度優先搜索

1.原理

在談論深度優先算法之前,我們可以先看看迷宮探索問題。下面是一個迷宮和圖之間的對應關係: 迷宮中的每一個交會點代表圖中的一個頂點,每一條通道對應一個邊。

迷宮探索可以採用Trémaux繩索探索法。即:

  • 選擇一條沒有標記過的通道,在你走過的路上鋪一條繩子;
  • 標記所有你第一次路過的路口和通道;
  • 當來到一個標記過的路口時(用繩子)回退到上個路口;
  • 當會退到的路口已經沒有可走的通道時繼續回退。

圖示如下:

下面是迷宮探索的一個小動畫:

接下來我們看圖的搜索方法。

2.代碼

定義一個edgesTo變量來後向記錄所有到s的頂點的記錄,和僅記錄從當前節點到起始節點不同,我們記錄圖中的每一個節點到開始節點的路徑。爲了完成這一日任務,通過設置edgesTo[w]=v,我們記錄從v到w的邊,換句話說,v-w是最後一條從s到達w的邊。 edgesTo[]其實是一個指向其父節點的樹。

點擊下載代碼


public class DepthFirstSearch {
    private boolean[] marked;//記錄是否被dfs訪問過
    private int[] edgesTo;//記錄最後一個到當前節點的頂點
    private int s;//搜索的起始點

    public DepthFirstSearch(Graph g, int s) {
        marked = new boolean[g.GetVerticals()];
        edgesTo = new int[g.GetVerticals()];
        this.s = s;
        dfs(g, s);
    }

    private void dfs(Graph g, int v) {
        marked[v] = true;
        for(Integer w : g.GetAdjacency(v)) {
            if (!marked[w]) {
                edgesTo[w] = v;
                dfs(g,w);
            }
        }
    }

    public boolean HasPathTo(int v) {
        return marked[v];
    }

    public Stack<Integer> PathTo(int v) {

        if (!HasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<Integer>();

        for (int x = v; x!=s; x=edgesTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    }

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        DepthFirstSearch dfs = new DepthFirstSearch(G, 0);
        Stack<Integer> path = dfs.PathTo(11);
        while(path.size() != 0) {
            System.out.print(path.pop() + " ");
        }       
    }
}

輸出:

0 5 4 9 12 11 

廣度優先搜索

1.原理

我們很自然地還經常對下面這些問題敢興趣。

單點最短路徑。給定一幅圖和一個起點s,回答“從s到給定目的頂點v是否存在一條路徑?如果有,找出其中最短的那條等類似問題。”

解決這個問題的經典方法叫做廣度優先搜索。

其主要原理是:

  • 將 s放到FIFO中,並且將s標記爲已訪問
  • 重複直到隊列爲空
  • 移除最近最近添加的頂點v
  • 將v未被訪問的節點添加到隊列中
  • 標記他們爲已經訪問

廣度優先是以距離遞增的方式來搜索路徑的。

點擊下載代碼


public class BreadthFirstSearch {
    private boolean[] marked;
    private int[] edgeTo;
    private int s;//搜索的起始點

    public BreadthFirstSearch(Graph g, int s) {
        marked=new boolean[g.GetVerticals()];
        edgeTo=new int[g.GetVerticals()];
        this.s = s;
        bfs(g, s);
    }

    private void bfs(Graph g, int s) {
        Queue<Integer> queue = new Queue<Integer>();
        marked[s] = true;
        queue.enqueue(s);
        while (queue.size()!=0) {
            int v = queue.dequeue();
            for(int w : g.GetAdjacency(v)) {
                if (!marked[w]) {
                    edgeTo[w] = v;
                    marked[w] = true;
                    queue.enqueue(w);
                }
            }
        }
    }

    public boolean HasPathTo(int v) {
        return marked[v];
    }

    public Stack<Integer> PathTo(int v) {
        if (!HasPathTo(v)) {
            return null;
        }

        Stack<Integer> path = new Stack<Integer>();
        for (int x = v; x!=s; x=edgeTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    } 

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        BreadthFirstSearch bfs = new BreadthFirstSearch(G, 0);
        Stack<Integer> path = bfs.PathTo(3);
        while(path.size() != 0) {
            System.out.print(path.pop() + " ");
        }       
    }
}

輸出:

0 5 4 9 11  (最短路徑)

總結

本文簡要介紹了無向圖中的深度優先和廣度優先算法,這兩種算法時圖處理算法中的最基礎算法,也是後續更復雜算法的基礎。它們搜索路徑如下:

附:關於stackqueue代碼下載

發佈了45 篇原創文章 · 獲贊 21 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章