13.4图的深度优先遍历
所谓图的遍历,即是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要特定策略,一般有两种访问策略:
1、 深度优先遍历
2、 广度优先遍历
图的深度首先遍历思路:
1、 深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解: 每次都在访问完当前结点后首先访问当前结点的第一个邻接结点
2、 这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问
3、 深度优先搜索是一个递归的过程
深度优先遍历算法步骤:
1、 访问初始结点 v,并标记结点 v 为已访问。
2、 查找结点 v 的第一个邻接节点w
3、 若 w 存在,则继续执行 4,如果 w 不存在,则回到第 1 步,将从 v 的下一个结点继续
4、 若 w 未被访问,对 w 进行深度优先遍历递归(即把 w 当做另一个 v,然后进行步骤 123)
5、 查找结点 v 的 w 邻接结点的下一个邻接结点,转到步骤 3
13.5图的广度优先遍历
图的广度优先搜索(Broad First Search):类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
广度优先遍历算法步骤:
1、 访问初始结点 v 并标记结点 v 为已访问
2、 结点 v 入队列
3、 当队列非空时,继续执行,否则算法结束
4、 出队列,取得队头结点 u
5、 查找结点 u 的第一个邻接结点 w
6、 若结点 u 的邻接结点 w 不存在,则转到步骤 3;否则循环执行以下三个步骤:
(1) 若结点 w 尚未被访问,则访问结点 w 并标记为已访问
(2) 结点 w 入队列
(3) 查找结点 u 的继 w 邻接结点后的下一个邻接结点 w,转到步骤 6
13.8图的深度优先VS广度优先
两种遍历方法进行遍历的结果:
package com.atguigu13.graph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
/**
* @author peng
* @date 2021/12/3 - 9:08
*
* 图的的实现
*/
public class Graph {
private ArrayList<String> vertexList;//使用ArrayList来存储图的顶点
private int[][] edges;//用于存储图的邻接矩阵
private int numberOfEdges;//用于存储图的边的个数
private boolean[] isVisited;//定义一个布尔数组用于存放该节点是否已经被访问
public static void main(String[] args) {
//测试图的实现
int n = 5;
String[] vertexs = {"A", "B", "C", "D", "E"};
Graph graph = new Graph(n);
//添加节点
for (String vertex : vertexs) {
graph.insertVertex(vertex);
}
//添加对应的边
graph.insertEdges(0,1,1);
graph.insertEdges(0,2,1);
graph.insertEdges(1,2,1);
graph.insertEdges(1,3,1);
graph.insertEdges(1,4,1);
//打印矩阵
graph.showGraph();
// //测试图的深度优先遍历
// System.out.println("图的深度优先遍历算法:");
// graph.dfs();
//测试图的广度优先搜索
System.out.println();
System.out.println("图的广度优先搜索:");
graph.dfs();
}
/**
* 构造器:初始化邻接矩阵
*/
public Graph(int n) {
vertexList = new ArrayList<String>(n);
edges = new int[n][n];
numberOfEdges = 0;
isVisited = new boolean[n];
}
/**
* 实现插入节点
*/
public void insertVertex(String vertex) {
//直接将新的节点插入到ArrayList中即可
vertexList.add(vertex);
}
/**
* 实现添加边的操作:v1、v2分别表示添加的边的两个顶点对应的坐标,weight表示该边是连接的还是不连接的,0表示不连接(默认是0,1表示连接)
*/
public void insertEdges(int v1, int v2, int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numberOfEdges++;
}
/**
* 实现返回图中节点的个数:直接返回ArrayListz中元素的个数即可
*/
public int getNumberOfVertex() {
return vertexList.size();
}
/**
* 返回图中的边的个数
*/
public int getNumberOfEdges() {
return numberOfEdges;
}
/**
* 返回索引对应的元素:0--->A, 1--->B
*/
public String getValueByIndex(int index) {
return vertexList.get(index);
}
/**
* 返回两个点之间的权值
*/
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
/**
* 显示邻接矩阵
*/
public void showGraph() {
for (int[] link : edges) {
//这是遍历矩阵的一个新方法:将矩阵一行一行进行输出
System.out.println(Arrays.toString(link));
}
}
//以下的方法是实现图的深度优先遍历算法
/**
* 返回当前节点的下一个节点的下标(下标是相对于ArrayList来说的,坐标是相对于邻接矩阵来说的),如果有则返回对应的下标,没有则返回-1
*/
public int getFirstNeighbor(int index) {
for (int i = 0; i < vertexList.size(); i++) {
//假设现在要找到A节点的连接的下一个节点是谁,以A节点为行,遍历寻找BCDE节点,看A节点与哪一个节点之间存在边,如果存在边表示
//这两个节点是相连的,就直接返回对应节点的下标,如果没有>0表示,这两个节点之间没有边,返回-1
if (edges[index][i] > 0) {
return i;
}
}
return -1;
}
/**
* 根据前一个邻接节点来获取下一个邻接节点
*/
public int getNextNeighbor(int v1, int v2) {
for (int i = v2 + 1; i < vertexList.size(); i++) {
if (edges[v1][i] > 0) {
return i;
}
}
return -1;
}
/**
* 实现深度优先遍历算法
*/
public void dfs(boolean[] isVisited, int v1) {
//第一步:遍历图的第一个节点,并标记为已遍历
System.out.print(getValueByIndex(v1) + "--->");
isVisited[v1] = true;
//第二步:找到v1节点的第一个邻接节点
int v2 = getFirstNeighbor(v1);
while (v2 != -1) {//表示存在该节点
//先判断有没有被访问过
if (isVisited[v2]) {
//如果该节点已经被访问过了,就继续寻找下一个节点
/**
* 在这里我们可以看到getFirstNeighbor和getNextNeighbor的区别
* getFirstNeighbor:是找到当前节点的第一个邻接节点
* getNextNeighbor:是找当前节点的邻接节点
* 两个方法在实质上是一样的,只不是将一个方法拆开了两个,这样使用起来的话会更加方便
*/
v2 = getNextNeighbor(v1, v2);
}else {
//如果找的这个节点没有被访问过,利用递归以该节点为起点进行遍历
dfs(isVisited, v2);
}
}
}
/**
* 对dfs进行重载:对所有的节点进行dfs
*/
public void dfs() {
for (int i = 0; i < getNumberOfVertex(); i++) {
if (!isVisited[i]) {
//如果当前节点还没有被访问过才能进行dfs
dfs(isVisited, i);
}
}
}
/**
* 对图进行广度优先遍历
*/
public void bfs(boolean[] isVisited, int v1) {
int u;//队列头节点对应的下标
int v;//邻接节点
//创建队列,记录节点访问的顺序
LinkedList queue = new LinkedList();
//访问节点,输出节点的信息
System.out.print(getValueByIndex(v1) + "--->");
//标记节点已经访问
isVisited[v1] = true;
//将节点加入队列
queue.addLast(v1);
while (!queue.isEmpty()) {
//如果当前队列是非空的
u = (Integer)queue.removeFirst();//取出队列的头节点进行遍历
v = getFirstNeighbor(u);//找到当前队列头的第一个邻接子节点
if (v != -1) {
//说明当前的邻接子点是存在的
if (!isVisited[v]) {
//如果当前的节点没有访问过
System.out.print(getValueByIndex(v) + "--->");//输出当前节点
isVisited[v] = true;
//入队
queue.addLast(v);
}
//如果这个邻接节点节点是已经访问过的了,那么就找当前节点的下一个邻接节点
v = getNextNeighbor(u, v);
}
}
}
/**
* 遍历所有的节点,都进行广度优先搜索
*/
public void bfs() {
for (int i = 0; i < getNumberOfVertex(); i++) {
if (!isVisited[i]){
//如果当前节点没有被访问过,就进行广度优先搜索
bfs(isVisited, i);
}
}
}
}