圖是一種數據結構,其中結點可以具有零個或多個相鄰元素。兩個結點之間的連接稱爲邊。 結點也可以稱爲頂點。如圖:
圖的存儲
在考慮代碼實現中,我們可以通過一個列表來存儲所有的結點,數組來存儲所有邊。
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++;
}
}
深度優先遍歷
我們以上面的圖來舉例說明圖的深度優先遍歷是怎麼樣的一個過程。
- 以A爲起始節點,找到它的第一個鄰接節點B;
- 接着以B爲起始節點(這裏其實是對B的一個遞歸過程),找到它的第一個鄰接節點C;
- 然後,以B爲起始節點(對C的一個遞歸過程),發現它的鄰接節點A和B都已經被訪問過了,退出C的遞歸;
- 這時候,需要對B進行回溯,找到B的第二個鄰接節點D;
- 再接着,以D爲起始節點(對D的一個遞歸過程),發現它的鄰接節點B已經被訪問過了,退出D的遞歸;
- 繼續對B進行回溯,找到B的第三個鄰接節點E;
- 再接着,以E爲起始節點(對E的一個遞歸過程),發現它的鄰接節點B已經被訪問過了,退出E的遞歸;
- 繼續對B進行回溯,發現沒有其他的鄰接節點了,退出B的遞歸;
- 此時,就需要對A進行回溯了,找到它的第二個鄰接點D,發現它的鄰接節點B已經被訪問過了,退出D的遞歸;
- 最後,又對A進行回溯, 沒有其他的鄰接節點了,完成對A的整個深度優先遍歷;
- 下面,就是對其他剩下的所有節點,都執行以上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;
}
廣度優先遍歷
廣度優先遍歷比深度優先遍歷簡單很多,比較容易理解。仍然以上面的圖爲例子:
- 從第一個節點A開始,依次找到它的所有鄰接節點;
- 然後,輪到第二個節點B,依次找到它的所有鄰接節點(未被訪問的);
- 剩下的節點類似…
/**
* 廣度優先遍歷方法
*/
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
疑問
爲什麼很多人對圖的廣度優先遍歷,都是通過隊列來實現的。就感覺是對前面的節點進行循環操作時,先幫後面的一些節點完成遍歷;
但,其實不用隊列的話,就是每個節點完成自己的遍歷。
有大神看到的幫忙解答一下!!!
歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。