圖的存儲、深度優先遍歷和廣度優先遍歷

圖是一種數據結構,其中結點可以具有零個或多個相鄰元素。兩個結點之間的連接稱爲邊。 結點也可以稱爲頂點。如圖:
在這裏插入圖片描述

圖的存儲

在考慮代碼實現中,我們可以通過一個列表來存儲所有的結點,數組來存儲所有邊。
在這裏插入圖片描述

package com.hong.graph;

import java.util.ArrayList;
import java.util.Arrays;

public class Graph {

    private ArrayList<String> vertexList; //存儲頂點集合
    private int[][] edges; //存儲圖對應的鄰結矩陣
    private int numOfEdges; //表示邊的數目
    //定義給數組boolean[], 記錄某個結點是否被訪問
    private boolean[] isVisited;

    //構造器
    public Graph(int n) {
        //初始化矩陣和vertexList
        edges = new int[n][n];
        vertexList = new ArrayList<String>(n);
        numOfEdges = 0;

    }

    //圖中常用的方法
    //顯示圖對應的矩陣
    public void showGraph() {
        for(int[] link : edges) {
            System.out.println(Arrays.toString(link));
        }
    }

    //插入結點
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }
    //添加邊
    /**
     *
     * @param v1 表示點的下標即使第幾個頂點  "A"-"B" "A"->0 "B"->1
     * @param v2 第二個頂點對應的下標
     * @param weight 表示
     */
    public void insertEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges++;
    }
}

深度優先遍歷

在這裏插入圖片描述
我們以上面的圖來舉例說明圖的深度優先遍歷是怎麼樣的一個過程。

  1. 以A爲起始節點,找到它的第一個鄰接節點B;
  2. 接着以B爲起始節點(這裏其實是對B的一個遞歸過程),找到它的第一個鄰接節點C;
  3. 然後,以B爲起始節點(對C的一個遞歸過程),發現它的鄰接節點A和B都已經被訪問過了,退出C的遞歸;
  4. 這時候,需要對B進行回溯,找到B的第二個鄰接節點D;
  5. 再接着,以D爲起始節點(對D的一個遞歸過程),發現它的鄰接節點B已經被訪問過了,退出D的遞歸;
  6. 繼續對B進行回溯,找到B的第三個鄰接節點E;
  7. 再接着,以E爲起始節點(對E的一個遞歸過程),發現它的鄰接節點B已經被訪問過了,退出E的遞歸;
  8. 繼續對B進行回溯,發現沒有其他的鄰接節點了,退出B的遞歸;
  9. 此時,就需要對A進行回溯了,找到它的第二個鄰接點D,發現它的鄰接節點B已經被訪問過了,退出D的遞歸;
  10. 最後,又對A進行回溯, 沒有其他的鄰接節點了,完成對A的整個深度優先遍歷;
  11. 下面,就是對其他剩下的所有節點,都執行以上10步的操作(但其實很多節點都已經被訪問過了,所以很多節點都是跳過的)。
/**
 * 深度優先遍歷主方法
 */
public void deepTraversing(){
    isVisited = new boolean[vertexList.size()];
    for (int i=0; i<vertexList.size(); i++){
        if (!isVisited[i]){
            deepTraversing(i);
        }
    }
}

/**
 * 以vertex爲起點,進行深度遍歷遞歸
 * @param vertex
 */
private void deepTraversing(int vertex){
    System.out.print(vertexList.get(vertex) + "->");
    isVisited[vertex] = true;

    // 獲取vertex的第一個鄰接節點
    int next = getNextEdges(vertex, 0);
    while (next != -1){
        if (!isVisited[next]){
            deepTraversing(next);
        } else{
            // 如果該鄰接節點已經被訪問過,則獲取下一個鄰接節點
            next = getNextEdges(vertex, next+1);
        }
    }

}

/**
 * 獲取vertex從初始位置爲start的下一個鄰接節點
 * @param vertex
 * @param start
 * @return
 */
private int getNextEdges(int vertex, int start){
    for (int j=start; j<vertexList.size(); j++){
        if (edges[vertex][j] == 1){
            return j;
        }
    }
    return -1;
}

廣度優先遍歷

在這裏插入圖片描述
廣度優先遍歷比深度優先遍歷簡單很多,比較容易理解。仍然以上面的圖爲例子:

  1. 從第一個節點A開始,依次找到它的所有鄰接節點;
  2. 然後,輪到第二個節點B,依次找到它的所有鄰接節點(未被訪問的);
  3. 剩下的節點類似…
/**
 * 廣度優先遍歷方法
 */
private void breadthTraversing(){
    isVisited = new boolean[vertexList.size()];
    int w;
    for (int v=0; v<vertexList.size(); v++){
        // 打印頂點v的所有鄰接節點(未被訪問過的)
        if (!isVisited[v]){
            System.out.print(vertexList.get(v) + "->");
            isVisited[v] = true;
        }
        w = getNextEdges(v, 0); // 獲取v的第一個鄰接節點
        while (w != -1){
            if (!isVisited[w]){
                System.out.print(vertexList.get(w) + "->");
                isVisited[w] = true;
            }
            w = getNextEdges(v, w+1);
        }
    }
}

完整代碼已上傳至GitHub

疑問

爲什麼很多人對圖的廣度優先遍歷,都是通過隊列來實現的。就感覺是對前面的節點進行循環操作時,先幫後面的一些節點完成遍歷;
但,其實不用隊列的話,就是每個節點完成自己的遍歷。
有大神看到的幫忙解答一下!!!
在這裏插入圖片描述

歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。在這裏插入圖片描述

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