參考文章: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;
}
}