圖的遍歷
- 圖的遍歷是和樹的遍歷類似,我們希望從圖中某一頂點出發(fā)訪遍圖中其余頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。
- 重復訪問頂點就不叫做遍歷了。
- 關于圖的基本概念,理論知識不想說了。太繁瑣~
- 直接上圖,這個應該都能看懂。
圖的深度優(yōu)先遍歷.png
- 左圖是一個圖,右圖是根據(jù)圖生成的矩陣。
[v0][v1]代表v0頂點到v1頂點的路徑權重為10
可以看見右圖,權重為0的都是頂點自己到自己,這根本就沒有意義。
而無限大符號表示到達不了,比如[v0][v2]。可以看左圖,v0只能到達v1和v5,到不了v2。
第一幅圖的矩陣可以看到有9個頂點,組成二維數(shù)組。
我們可以先生成這個矩陣:
public class Graph {
private int vertexSize; // 頂點數(shù)量
private int[] vertexs; // 頂點數(shù)組
private int[][] matrix; // 包含所有頂點的數(shù)組
// 路徑權重
// 0意味著頂點自己到自己,無意義
// MAX_WEIGHT也意味著到目的頂點不可達
private static final int MAX_WEIGHT = 1000;
public Graph(int vertextSize) {
this.vertexSize = vertextSize;
matrix = new int[vertextSize][vertextSize];
vertexs = new int[vertextSize];
for (int i = 0; i < vertextSize; i++) {
vertexs[i] = i;
}
}
public static void main(String[] args) {
Graph graph = new Graph(9);
// 頂點的矩陣設置
int[] a1 = new int[] { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
int[] a2 = new int[] { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
int[] a3 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, 24, 16, 21 };
//int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
int[] a5 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
int[] a6 = new int[] { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
//int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
int[] a8 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
int[] a9 = new int[] { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
graph.matrix[0] = a1;
graph.matrix[1] = a2;
graph.matrix[2] = a3;
graph.matrix[3] = a4;
graph.matrix[4] = a5;
graph.matrix[5] = a6;
graph.matrix[6] = a7;
graph.matrix[7] = a8;
graph.matrix[8] = a9;
}
}
- 可以看到我們用一個
matrix
二維數(shù)組存放所有頂點 -
0
表示自己到自己,MAX_WEIGHT
表示無限大 - 然后就是一些實例化和設置矩陣
- 有了這些,我們才能進行遍歷
深度優(yōu)先遍歷
- 深度優(yōu)先遍歷(Depth First Search),也有稱為深度優(yōu)先搜索,簡稱為DFS。
深度優(yōu)先遍歷.png
- 遍歷規(guī)則:不斷的沿著頂點的深度方向遍歷。頂點的深度方向是指它的鄰接點方向。
- 它從圖中某個頂點v出發(fā),訪問此頂點,然后從頂點的未被訪問的鄰接點出發(fā)深度優(yōu)先遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。
- 簡單說,就是頂點將第一個鄰接點當作左孩子,其它鄰接點都當做右孩子。最后排成一棵樹。
- 深度優(yōu)先遍歷是指先遍歷到最深層次,然后再探索鄰接點,接著又遍歷最深層次。二叉樹的先序遍歷就是一種深度優(yōu)先遍歷。
======================================
- 思想與步驟:
- 看上圖右圖,我們先訪問A,然后訪問A的第一個鄰接點B。接著訪問B的第一個鄰接點C。。。。最后訪問到F,F(xiàn)想訪問第一個鄰接點A。但是A已經(jīng)訪問過了,只能訪問F的下一個鄰接點G。
- 這就是深度優(yōu)先遍歷的訪問順序。
- 在代碼中,依照分析。獲取某頂點的第一個鄰接點和下一個臨界點是經(jīng)常使用的方法。訪問過程中,我們要判斷該頂點是否已訪問過,這個也需要輔助變量。
private boolean[] isVisited = new boolean[vertextSize];
/**
* 獲取指定頂點的第一個鄰接點
*
* @param index
* 指定鄰接點
* @return
*/
private int getFirstNeighbor(int index) {
for (int i = 0; i < vertexSize; i++) {
if (matrix[index][i] < MAX_WEIGHT && matrix[index][i] > 0) {
return i;
}
}
return -1;
}
/**
* 獲取指定頂點的下一個鄰接點
*
* @param v
* 指定的頂點
* @param index
* 從哪個鄰接點開始
* @return
*/
private int getNextNeighbor(int v, int index) {
for (int i = index+1; i < vertexSize; i++) {
if (matrix[v][i] < MAX_WEIGHT && matrix[v][i] > 0) {
return i;
}
}
return -1;
}
核心代碼很簡單,經(jīng)上述分析過后:
/**
* 圖的深度優(yōu)先遍歷算法
*/
private void depthFirstSearch(int i) {
isVisited[i] = true;
int w = getFirstNeighbor(i);
while (w != -1) {
if (!isVisited[w]) {
// 需要遍歷該頂點
System.out.println("訪問到了:" + w + "頂點");
depthFirstSearch(w); // 進行深度遍歷
}
w = getNextNeighbor(i, w); // 第一個相對于w的鄰接點
}
}
- 傳
0
進去,表示v0
。 - 設置
v0
已訪問過,獲取v0
的第一個鄰接點。w != -1
說明有這個鄰接點,然后對這個臨界點進行判斷。- 已訪問,那就找下一個臨界點
- 未訪問,進行訪問,然后對該鄰接點進行深度優(yōu)先遍歷。
- 算法還是很簡單的!
廣度優(yōu)先遍歷
- 思想(感悟):
- 廣度優(yōu)先遍歷表示把每一層都遍歷完才能遍歷下一層。
- 我們來思考:假設
v0
有3個鄰接點,v1 v2 v3
。- 我們訪問v0后,然后訪問v1 v2 v3。完畢后我們要從v1開始遍歷它的鄰接點,接著從v2開始遍歷它的鄰接點,最后是從v3開始遍歷它的鄰接點。
- 也就是說,3個鄰接點訪問完后。我們要回過頭逐個遍歷它們的鄰接點。這一點我覺得要用個容器把它們順序存儲下來。然后每次從容器首部取出一個頂點開始遍歷。這里我想到
LinkedList
,因為它適合增刪。而且這里不需要遍歷集合。
- 整體步驟:
- 我們可以把第一個頂點放進集合,然后
while(!queue.isEmpty())
或while(queue.size() > 0)
都行。開始循環(huán)。 - 然后取出并刪除集合中第一個頂點元素的第一個鄰接點。對這個頂點進行訪問,
- 如果該頂點未訪問過,就訪問!然后將該頂點放入集合。
- 如果該頂點已訪問過,就找該頂點的下一個鄰接點。
- 我們可以把第一個頂點放進集合,然后
核心代碼:
/**
* 圖的廣度優(yōu)先遍歷算法
*/
private void boardFirstSearch(int i) {
LinkedList<Integer> queue = new LinkedList<>();
System.out.println("訪問到了:" + i + "頂點");
isVisited[i] = true;
queue.add(i);
while (queue.size() > 0) {
int w = queue.removeFirst().intValue();
int n = getFirstNeighbor(w);
while (n != -1) {
if (!isVisited[n]) {
System.out.println("訪問到了:" + n + "頂點");
isVisited[n] = true;
queue.add(n);
}
n = getNextNeighbor(w, n);
}
}
}
完整代碼
復制即可運行
import java.util.LinkedList;
public class Graph {
private int vertexSize; // 頂點數(shù)量
private int[] vertexs; // 頂點數(shù)組
private int[][] matrix; // 包含所有頂點的數(shù)組
// 路徑權重
// 0意味著頂點自己到自己,無意義
// MAX_WEIGHT也意味著到目的頂點不可達
private static final int MAX_WEIGHT = 1000;
private boolean[] isVisited; // 某頂點是否被訪問過
public Graph(int vertextSize) {
this.vertexSize = vertextSize;
matrix = new int[vertextSize][vertextSize];
vertexs = new int[vertextSize];
for (int i = 0; i < vertextSize; i++) {
vertexs[i] = i;
}
isVisited = new boolean[vertextSize];
}
/**
* 獲取指定頂點的第一個鄰接點
*
* @param index
* 指定鄰接點
* @return
*/
private int getFirstNeighbor(int index) {
for (int i = 0; i < vertexSize; i++) {
if (matrix[index][i] < MAX_WEIGHT && matrix[index][i] > 0) {
return i;
}
}
return -1;
}
/**
* 獲取指定頂點的下一個鄰接點
*
* @param v
* 指定的頂點
* @param index
* 從哪個鄰接點開始
* @return
*/
private int getNextNeighbor(int v, int index) {
for (int i = index+1; i < vertexSize; i++) {
if (matrix[v][i] < MAX_WEIGHT && matrix[v][i] > 0) {
return i;
}
}
return -1;
}
/**
* 圖的深度優(yōu)先遍歷算法
*/
private void depthFirstSearch(int i) {
isVisited[i] = true;
int w = getFirstNeighbor(i);
while (w != -1) {
if (!isVisited[w]) {
// 需要遍歷該頂點
System.out.println("訪問到了:" + w + "頂點");
depthFirstSearch(w); // 進行深度遍歷
}
w = getNextNeighbor(i, w); // 第一個相對于w的鄰接點
}
}
/**
* 圖的廣度優(yōu)先遍歷算法
*/
private void boardFirstSearch(int i) {
LinkedList<Integer> queue = new LinkedList<>();
System.out.println("訪問到了:" + i + "頂點");
isVisited[i] = true;
queue.add(i);
while (queue.size() > 0) {
int w = queue.removeFirst().intValue();
int n = getFirstNeighbor(w);
while (n != -1) {
if (!isVisited[n]) {
System.out.println("訪問到了:" + n + "頂點");
isVisited[n] = true;
queue.add(n);
}
n = getNextNeighbor(w, n);
}
}
}
public static void main(String[] args) {
Graph graph = new Graph(9);
// 頂點的矩陣設置
int[] a1 = new int[] { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
int[] a2 = new int[] { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
int[] a3 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, 24, 16, 21 };
//int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
int[] a5 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
int[] a6 = new int[] { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
//int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
int[] a8 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
int[] a9 = new int[] { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
graph.matrix[0] = a1;
graph.matrix[1] = a2;
graph.matrix[2] = a3;
graph.matrix[3] = a4;
graph.matrix[4] = a5;
graph.matrix[5] = a6;
graph.matrix[6] = a7;
graph.matrix[7] = a8;
graph.matrix[8] = a9;
graph.depthFirstSearch(0);
//graph.boardFirstSearch(0);
}
}