圖的廣度優先搜索(BFS)與圖的深度優先搜索(DFS)詳解及其代碼實現

參考文章:https://blog.csdn.net/weixin_40953222/article/details/80544928

1.概述

與樹的遍歷類似,圖的遍歷也是從圖中某個頂點出發,然後按照某種方法對圖中所有頂點進行訪問,且僅訪問一次
但是圖的遍歷相對樹而言要更爲複雜,因爲圖中的任意頂點都可能與其他頂點相鄰,所以在圖的遍歷中必須記錄已被訪問的頂點,避免重複訪問
根據搜索路徑的不同,可以將遍歷圖的方法分爲兩種:廣度優先搜索(BFS)與深度優先搜索(DFS)

注意:圖的廣度優先遍歷(搜索) 與 圖的深度優先遍歷(搜索) 不一定 會得到相同的頂點輸出順序

2.圖的基本概念

2.1無向圖與有向圖

頂點對(u,v)是無序的,即(u,v)和(v,u)是同一條邊,常用一對圓括號表示

                                         圖2.1.1 無向圖

頂點對<u,v>是有序的,它是指從頂點u到頂點 v的一條有向邊,其中u是有向邊的始點,v是有向邊的終點,常用一對尖括號表示

                                  圖2.1.2 有向圖

2.2權與網

圖的每條邊上可能存在具有某種含義的數值,稱該數值爲該邊上的權,而這種帶權的圖被稱爲網

3.廣度優先搜索(BFS)

3.1廣度優先搜索的基本思路

廣度優先搜索類似於樹的層次遍歷過程,它需要藉助一個隊列來實現,如圖2.1.1所示,要想遍歷從v0到v6的每一個頂點,我們可以設v0爲第一層,v1、v2、v3爲第二層,v4、v5爲第三層,v6爲第四層,再逐個遍歷每一層的每個頂點
具體過程如下:
1.創建一個visited數組,用來記錄已經被訪問過的頂點,創建一個隊列,用來存放每一層的頂點,初始化圖G
2.從圖中的v0開始訪問,將visited[v0]的值設置爲true,表示該頂點已經被訪問,同時將v0入隊
3.只要隊列不空,則重複如下操作:
(1)隊頭頂點u出隊
(2)依次檢查u的所有鄰接頂點w,若visited[w]的值爲false,則訪問w,並將visited[w]置爲true,同時將w入隊
3.2圖解過程

白色表示未被訪問,黑色表示已訪問
visited數組:0表示未訪問,1表示以訪問
隊列:隊頭出元素,隊尾進元素

1.初始時全部頂點均未被訪問,visited數組初始化爲0,隊列中沒有元素

2.訪問頂點v0,並置visited[0]的值爲1,同時將v0入隊

3.將v0出隊,訪問v0的鄰接點v2,判斷visited[2],因爲visited[2]的值爲0,訪問v2 ,將visited[2]置爲1,並將v2入隊

4.訪問v0鄰接點v1,判斷visited[1],因爲visited[1]的值爲0,訪問v1 ,將visited[1]置爲0,並將v1入隊

5.訪問v0鄰接點v3,判斷visited[3],因爲visited[3]的值爲0,訪問v3, 將visited[3]置爲0,並將v3入隊 

6.v0的全部鄰接點均已被訪問完畢,將隊頭元素v2出隊,開始訪問v2的所有鄰接點

開始訪問v2鄰接點v0,判斷visited[0],因爲其值爲1,不進行訪問

繼續訪問v2鄰接點v4,判斷visited[4],因爲其值爲0,訪問v4,將visited[4]置爲1,並將v4入隊

7. v2的全部鄰接點均已被訪問完畢,將隊頭元素v1出隊,開始訪問v1的所有鄰接點

開始訪問v1鄰接點v0,因爲visited[0]值爲1,不進行訪問

繼續訪問v1鄰接點v4,因爲visited[4]的值爲1,不進行訪問

繼續訪問v1鄰接點v5,因爲visited[5]值爲0,訪問v5,將visited[5]置爲1,並將v5入隊

8.v1的全部鄰接點均已被訪問完畢,將隊頭元素v3出隊,開始訪問v3的所有鄰接點

開始訪問v3鄰接點v0,因爲visited[0]值爲1,不進行訪問

繼續訪問v3鄰接點v5,因爲visited[5]值爲1,不進行訪問

9.v3的全部鄰接點均已被訪問完畢,將隊頭元素v4出隊,開始訪問v4的所有鄰接點

開始訪問v4的鄰接點v2,因爲visited[2]的值爲1,不進行訪問

繼續訪問v4的鄰接點v1,因爲visited[1]的值爲1,不進行訪問

繼續訪問v4的鄰接點v6,因爲visited[6]的值爲0,訪問v6,將visited[6]值爲1,並將v6入隊

10.v4的全部鄰接點均已被訪問完畢,將隊頭元素v5出隊,開始訪問v5的所有鄰接點

開始訪問v5鄰接點v1,因爲visited[1]的值爲1,不進行訪問

繼續訪問v5鄰接點v3,因爲visited[3]的值爲1,不進行訪問

繼續訪問v5鄰接點v6,因爲visited[6]的值爲1,不進行訪問

11.v5的全部鄰接點均已被訪問完畢,將隊頭元素v6出隊,開始訪問v6的所有鄰接點

開始訪問v6鄰接點v4,因爲visited[4]的值爲1,不進行訪問

繼續訪問v6鄰接點v5,因爲visited[5]的值文1,不進行訪問

12.隊列爲空,退出循環,全部頂點均訪問完畢

3.3代碼實現

核心代碼

/**
 * 圖的廣度優先遍歷 算法
 *
 * @param initVertex 從哪個頂點開始遍歷
 */
public void bfs(String initVertex) {
    // 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
    boolean[] visited = new boolean[this.vertexList.size()];
    // 記錄頂點的訪問順序,用linkedList模擬隊列
    LinkedList<String> linkedList = new LinkedList<>();

    // 獲取initVertex頂點在圖中頂點集合的對應索引
    int indexVertex = this.getIndexOfVertex(initVertex);

    // 輸出頂點initVertex
    System.out.print(initVertex + "\t");
    // 標記爲已經被訪問
    visited[indexVertex] = true;
    // 加入到隊列中
    linkedList.addLast(initVertex);

    while(!linkedList.isEmpty()) {
        // 取出隊列linkedList中的第一個元素
        String vertex = linkedList.removeFirst();
        // 獲取頂點vertex在圖中頂點集合的對應索引
        int index = this.getIndexOfVertex(vertex);
        // 記錄頂點所在集合的索引對應的值
        String valueOfVertex;
        for(int i = 0; i < this.vertexList.size(); i++) {
            // 隊列中取出的頂點 都要與 圖的頂點集合中所有頂點進行遍歷比較
            // this.edgeArray[index][i] > 0 頂點連通
            // !visited[i] 頂點未被訪問
            if(this.edgeArray[index][i] > 0 && !visited[i]) {
                // 獲取頂點所在集合的索引對應的值
                valueOfVertex = this.getValueOfVertex(i);
                System.out.print(valueOfVertex + "\t");
                // 標記爲已經被訪問
                visited[i] = true;
                // 加入到隊列中
                linkedList.addLast(valueOfVertex);
            }
        }
    }
}

完整代碼 

package com.zzb.datastructure.graph;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @Auther: Administrator
 * @Date: 2020/3/27 12:50
 * @Description: 圖的廣度優先遍歷(搜索) 與 圖的深度優先遍歷(搜索) 代碼實現
 *
 * 注意:圖的廣度優先遍歷(搜索) 與 圖的深度優先遍歷(搜索) 不一定 會得到相同的頂點輸出順序
 */
public class BFSandDFSofGraph {
    public static void main(String[] args) {
        // 頂點總數
        int totalOfVertex = 5;
        // 頂點值
        String[] vertexArray = {"A", "B", "C", "D", "E"};
        // 創建圖對象
        Graph graph = new Graph(totalOfVertex);
        // 添加定點
        for(int i = 0; i < vertexArray.length; i++) {
            graph.addVertex(vertexArray[i]);
        }
        // 添加邊
        // A-B A-C B-C B-D B-E
        graph.addEdge(0, 1, 1);
		graph.addEdge(0, 2, 1);
		graph.addEdge(1, 2, 1);
		graph.addEdge(1, 3, 1);
		graph.addEdge(1, 4, 1);
		// 顯示圖的鄰接矩陣
        graph.showGraph();
      /*0	1	1	0	0
        1	0	1	1	1
        1	1	0	0	0
        0	1	0	0	0
        0	1	0	0	0*/
        // 圖的廣度優先遍歷
        graph.bfs("A");
        /*A	  B	  C	  D	  E*/
    }
}

/**
 * 圖對象
 */
class Graph implements Serializable {
    private static final long serialVersionUID = -6363517010637626584L;

    // 存儲圖中各個頂點的集合
    private List<String> vertexList;
    // 存儲圖中各條邊的鄰接矩陣
    private int[][] edgeArray;
    // 存儲圖中邊的總數
    private int totalOfedge;

    /**
     * 構造初始化
     *
     * @param totalOfVertex 頂點個數
     */
    public Graph(int totalOfVertex) {
        // 頂點個數
        vertexList = new ArrayList<>(totalOfVertex);
        // 鄰接矩陣大小
        edgeArray = new int[totalOfVertex][totalOfVertex];
        // 邊的總數
        totalOfedge = 0;
    }

    /**
     * 圖的廣度優先遍歷 算法
     *
     * @param initVertex 從哪個頂點開始遍歷
     */
    public void bfs(String initVertex) {
        // 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
        boolean[] visited = new boolean[this.vertexList.size()];
        // 記錄頂點的訪問順序,用linkedList模擬隊列
        LinkedList<String> linkedList = new LinkedList<>();

        // 獲取initVertex頂點在圖中頂點集合的對應索引
        int indexVertex = this.getIndexOfVertex(initVertex);

        // 輸出頂點initVertex
        System.out.print(initVertex + "\t");
        // 標記爲已經被訪問
        visited[indexVertex] = true;
        // 加入到隊列中
        linkedList.addLast(initVertex);

        while(!linkedList.isEmpty()) {
            // 取出隊列linkedList中的第一個元素
            String vertex = linkedList.removeFirst();
            // 獲取頂點vertex在圖中頂點集合的對應索引
            int index = this.getIndexOfVertex(vertex);
            // 記錄頂點所在集合的索引對應的值
            String valueOfVertex;
            for(int i = 0; i < this.vertexList.size(); i++) {
                // 隊列中取出的頂點 都要與 圖的頂點集合中所有頂點進行遍歷比較
                // this.edgeArray[index][i] > 0 頂點連通
                // !visited[i] 頂點未被訪問
                if(this.edgeArray[index][i] > 0 && !visited[i]) {
                    // 獲取頂點所在集合的索引對應的值
                    valueOfVertex = this.getValueOfVertex(i);
                    System.out.print(valueOfVertex + "\t");
                    // 標記爲已經被訪問
                    visited[i] = true;
                    // 加入到隊列中
                    linkedList.addLast(valueOfVertex);
                }
            }
        }

    }

    /**
     * 獲取頂點個數
     *
     * @return 獲取頂點個數
     */
    public int getTotalOfvertex() {
        return this.vertexList.size();
    }

    /**
     * 顯示圖對應的鄰接矩陣
     */
    public void showGraph() {
        for(int i = 0; i < vertexList.size(); i++) {
            for(int j = 0; j < vertexList.size(); j++) {
                System.out.print(edgeArray[i][j] + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 獲取邊的總數
     *
     * @return 獲取邊的總數
     */
    public int getTotalOfedge() {
        return this.totalOfedge;
    }

    /**
     * 獲取各個定點所對應的值
     *
     * @param indexOfVertex 頂點所在集合的索引
     * @return 獲取各個頂點所對應的值
     */
    public String getValueOfVertex(int indexOfVertex) {
        return this.vertexList.get(indexOfVertex);
    }

    /**
     * 獲取各個定點所對應的索引
     *
     * @param vertex 頂點所在集合的值
     * @return 獲取各個頂點所對應的索引
     */
    public int getIndexOfVertex(String vertex) {
        for(int i = 0; i < this.vertexList.size(); i++) {
            if(vertex.equals(this.vertexList.get(i))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 獲取兩個頂點之間的權值
     *
     * @param vertex1
     * @param vertex2
     * @return 獲取兩個頂點之間的權值
     */
    public int getWeight(int vertex1, int vertex2) {
        return this.edgeArray[vertex1][vertex2];
    }

    /**
     * 向圖中添加頂點
     *
     * @param vertex
     */
    public void addVertex(String vertex) {
        this.vertexList.add(vertex);
    }

    /**
     * 向圖中添加邊
     *
     * @param vertex1
     * @param vertex2
     * @param weight
     */
    public void addEdge(int vertex1, int vertex2, int weight) {
        edgeArray[vertex1][vertex2] = weight; // 無向圖
        edgeArray[vertex2][vertex1] = weight; // 無向圖
        this.totalOfedge++;
    }
}

4.深度優先搜索(DFS)

4.1深度優先搜索的基本思路

深度優先搜索類似於樹的先序遍歷,具體過程如下:
準備工作:創建一個visited數組,用於記錄所有被訪問過的頂點
1.從圖中v0出發,訪問v0
2.找出v0的第一個未被訪問的鄰接點,訪問該頂點,以該頂點爲新頂點,重複此步驟,直至剛訪問過的頂點沒有未被訪問的鄰接點爲止
3.返回前一個訪問過的仍有未被訪問鄰接點的頂點,繼續訪問該頂點的下一個未被訪問領接點
4.重複2,3步驟,直至所有頂點均被訪問,搜索結束

4.2圖解過程

待續...

4.3代碼實現

核心代碼

/**
 * 圖的深度優先遍歷 算法
 * @param visited 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
 * @param initVertex 從哪個頂點開始遍歷
 */
public void dfs(boolean[] visited, String initVertex) {
    // 輸出頂點initVertex
    System.out.print(initVertex + "\t");

    // 獲取initVertex頂點在圖中頂點集合的對應索引
    int indexVertex = this.getIndexOfVertex(initVertex);

    // 標記爲已經被訪問
    visited[indexVertex] = true;

    // 遞歸調用
    for(int i = 0; i < this.vertexList.size(); i++) {
        if(this.edgeArray[indexVertex][i] > 0 && !visited[i]) {
            dfs(visited, this.getVertexList().get(i));
        }
    }
}

完整代碼(包括廣度優先搜索代碼在內)

package com.zzb.datastructure.graph;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @Auther: Administrator
 * @Date: 2020/3/27 12:50
 * @Description: 圖的廣度優先遍歷(搜索) 與 圖的深度優先遍歷(搜索) 代碼實現
 *
 * 注意:圖的廣度優先遍歷(搜索) 與 圖的深度優先遍歷(搜索) 不一定 會得到相同的頂點輸出順序
 */
public class BFSandDFSofGraph {
    public static void main(String[] args) {
        // 頂點總數
//        int totalOfVertex = 5;
        int totalOfVertex = 8;

        // 頂點值
//        String[] vertexArray = {"A", "B", "C", "D", "E"};
        String[] vertexArray = {"1", "2", "3", "4", "5", "6", "7", "8"};

        // 創建圖對象
        Graph graph = new Graph(totalOfVertex);
        // 添加定點
        for(int i = 0; i < vertexArray.length; i++) {
            graph.addVertex(vertexArray[i]);
        }

        // 添加邊
        // A-B A-C B-C B-D B-E
        /*graph.addEdge(0, 1, 1);
		graph.addEdge(0, 2, 1);
		graph.addEdge(1, 2, 1);
		graph.addEdge(1, 3, 1);
		graph.addEdge(1, 4, 1);*/

        // 添加邊
        graph.addEdge(0, 1, 1);
        graph.addEdge(0, 2, 1);
        graph.addEdge(1, 3, 1);
        graph.addEdge(1, 4, 1);
        graph.addEdge(3, 7, 1);
        graph.addEdge(4, 7, 1);
        graph.addEdge(2, 5, 1);
        graph.addEdge(2, 6, 1);
        graph.addEdge(5, 6, 1);

		// 顯示圖的鄰接矩陣
//        graph.showGraph();
        // 圖的廣度優先遍歷
        graph.bfs("1");
        /*1	2	3	4	5	6	7	8*/

        System.out.println();

        // 圖的深度優先遍歷
        // 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
        boolean[] visited = new boolean[graph.getVertexList().size()];
        graph.dfs(visited, "1");
        /*1	2	4	8	5	3	6	7*/
    }
}

/**
 * 圖對象
 */
class Graph implements Serializable {
    private static final long serialVersionUID = -6363517010637626584L;

    // 存儲圖中各個頂點的集合
    private List<String> vertexList;
    // 存儲圖中各條邊的鄰接矩陣
    private int[][] edgeArray;
    // 存儲圖中邊的總數
    private int totalOfedge;

    /**
     * 構造初始化
     *
     * @param totalOfVertex 頂點個數
     */
    public Graph(int totalOfVertex) {
        // 頂點個數
        vertexList = new ArrayList<>(totalOfVertex);
        // 鄰接矩陣大小
        edgeArray = new int[totalOfVertex][totalOfVertex];
        // 邊的總數
        totalOfedge = 0;
    }

    /**
     * 圖的廣度優先遍歷 算法
     *
     * @param initVertex 從哪個頂點開始遍歷
     */
    public void bfs(String initVertex) {
        // 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
        boolean[] visited = new boolean[this.vertexList.size()];
        // 記錄頂點的訪問順序,用linkedList模擬隊列
        LinkedList<String> linkedList = new LinkedList<>();

        // 獲取initVertex頂點在圖中頂點集合的對應索引
        int indexVertex = this.getIndexOfVertex(initVertex);

        // 輸出頂點initVertex
        System.out.print(initVertex + "\t");
        // 標記爲已經被訪問
        visited[indexVertex] = true;
        // 加入到隊列中
        linkedList.addLast(initVertex);

        while(!linkedList.isEmpty()) {
            // 取出隊列linkedList中的第一個元素
            String vertex = linkedList.removeFirst();
            // 獲取頂點vertex在圖中頂點集合的對應索引
            int index = this.getIndexOfVertex(vertex);
            // 記錄頂點所在集合的索引對應的值
            String valueOfVertex;
            for(int i = 0; i < this.vertexList.size(); i++) {
                // 隊列中取出的頂點 都要與 圖的頂點集合中所有頂點進行遍歷比較
                // this.edgeArray[index][i] > 0 頂點連通
                // !visited[i] 頂點未被訪問
                if(this.edgeArray[index][i] > 0 && !visited[i]) {
                    // 獲取頂點所在集合的索引對應的值
                    valueOfVertex = this.getValueOfVertex(i);
                    System.out.print(valueOfVertex + "\t");
                    // 標記爲已經被訪問
                    visited[i] = true;
                    // 加入到隊列中
                    linkedList.addLast(valueOfVertex);
                }
            }
        }
    }

    /**
     * 圖的深度優先遍歷 算法
     * @param visited 記錄頂點是否已經被訪問,數組的下標即爲頂點在圖中頂點集合的索引
     * @param initVertex 從哪個頂點開始遍歷
     */
    public void dfs(boolean[] visited, String initVertex) {
        // 輸出頂點initVertex
        System.out.print(initVertex + "\t");

        // 獲取initVertex頂點在圖中頂點集合的對應索引
        int indexVertex = this.getIndexOfVertex(initVertex);

        // 標記爲已經被訪問
        visited[indexVertex] = true;

        // 遞歸調用
        for(int i = 0; i < this.vertexList.size(); i++) {
            if(this.edgeArray[indexVertex][i] > 0 && !visited[i]) {
                dfs(visited, this.getVertexList().get(i));
            }
        }
    }

    /**
     * 獲取頂點個數
     *
     * @return 獲取頂點個數
     */
    public int getTotalOfvertex() {
        return this.vertexList.size();
    }

    /**
     * 顯示圖對應的鄰接矩陣
     */
    public void showGraph() {
        for(int i = 0; i < vertexList.size(); i++) {
            for(int j = 0; j < vertexList.size(); j++) {
                System.out.print(edgeArray[i][j] + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 獲取各個定點所對應的值
     *
     * @param indexOfVertex 頂點所在集合的索引
     * @return 獲取各個頂點所對應的值
     */
    public String getValueOfVertex(int indexOfVertex) {
        return this.vertexList.get(indexOfVertex);
    }

    /**
     * 獲取各個定點所對應的索引
     *
     * @param vertex 頂點所在集合的值
     * @return 獲取各個頂點所對應的索引
     */
    public int getIndexOfVertex(String vertex) {
        for(int i = 0; i < this.vertexList.size(); i++) {
            if(vertex.equals(this.vertexList.get(i))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 獲取兩個頂點之間的權值
     *
     * @param vertex1
     * @param vertex2
     * @return 獲取兩個頂點之間的權值
     */
    public int getWeight(int vertex1, int vertex2) {
        return this.edgeArray[vertex1][vertex2];
    }

    /**
     * 向圖中添加頂點
     *
     * @param vertex
     */
    public void addVertex(String vertex) {
        this.vertexList.add(vertex);
    }

    /**
     * 向圖中添加邊
     *
     * @param vertex1
     * @param vertex2
     * @param weight
     */
    public void addEdge(int vertex1, int vertex2, int weight) {
        edgeArray[vertex1][vertex2] = weight; // 無向圖
        edgeArray[vertex2][vertex1] = weight; // 無向圖
        this.totalOfedge++;
    }

    public List<String> getVertexList() {
        return vertexList;
    }

    public void setVertexList(List<String> vertexList) {
        this.vertexList = vertexList;
    }

    public int[][] getEdgeArray() {
        return edgeArray;
    }

    public void setEdgeArray(int[][] edgeArray) {
        this.edgeArray = edgeArray;
    }

    /**
     * 獲取邊的總數
     *
     * @return 獲取邊的總數
     */
    public int getTotalOfedge() {
        return this.totalOfedge;
    }

    public void setTotalOfedge(int totalOfedge) {
        this.totalOfedge = totalOfedge;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章