PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接
目录
1、图的广度优先遍历
1、1 图的广度优先遍历基本思想
1、2 图的广度优先遍历算法步骤
1、3 图的广度优先遍历代码实现
1、图的广度优先遍历
1、1 图的广度优先遍历基本思想
在图的表现形式第一篇(Java实现)这篇文章中,我们学习了图的深度优先遍历,这篇文章我们主要是学习一下图的广度优先遍历,这里我先介绍一下图的广度优先遍历基本思想,为了方便好理解这个思想,我先画一张图,如图1所示;
图片
它的思想:
(1)广度优先遍历,类似于分层搜索的过程;就拿图1来举例,A 顶点为第一层,B、C 顶点为第二层,D、E、F、G 顶点为第三层,H 顶点为第四层,搜索的过程先从第一层开始搜索,然后往下层搜索,这总能理解吧?
(2)广度优先遍历需要使用一个队列来保持访问过的节点的顺序,以便按照这个顺序来访问这些节点的邻接节点;就拿图1来举例,一开始先保存 A 顶点,然后把 A 顶点先访问,访问完 A 顶点后,把 A 顶点从队列中删除,然后再 A 顶点的所有邻接顶点(B、C)进行访问并把它们加入到队列中去,访问完 B、C 之后,又把 B 的邻接顶点(D、E)和 C 的邻接顶点(F、G)进行访问并把B、C 顶点进行删除,B、C 以下层的顶点以此类推......,其实说白了就是一层一层的进行访问,图1的广度优先遍历结果为 A->B->C->D->E->F->G->H 。
1、2 图的广度优先遍历算法步骤
说图的广度优先遍历算法步骤之前,我先以图1为例子,列举实现图1所用到的一些变量;
顶点数量 vertexNum 为8个,顶点列表 lstVertex 为 A、B、C、D、E、F、G、H,边的数量 edgeNum 为8,一开始队列 lstSearch 为空;用 isVisited 数组标记每个顶点是否被访问过,默认是没有被访问过,也就是每个元素值为 false ;边的二维数组 vertexPathArray 如图2所示(说明:1表示2个顶点直接相连接,0表示2个顶点没有直接相连接);
图片
它的算法步骤(注意:算法步骤看不懂的话,先看下面的代码实现,再回过头来看算法步骤就好理解了):
(1)访问初始节点 index 并标记为已访问,其中 index 为顶点列表的索引,就拿图1来举例,index 为0 ,那么将 lstVertex.get(0) 取出来进行访问并将 isVisited[0] 置为 true,表示 lstVertex 的第0个元素已经被访问过。
(2)节点索引为 index 的入队列,广度遍历需要维护一个局部队列 lstSearch 。
(3)判断局部队列 lstSearch 的大小是否为0(一开始 lstSearch 的大小一定不为0),如果为0,那么就不执行从(4)开始之后的步骤;如果 lstSearch 的大小不为0,往下走(4)。
(4)如果 lstSearch 的大小不为0,那么就删除 lstSearch 的第一个索引并返回得到一个顶点列表 lstVertex 元素的索引(用 currIndex 表示删除 lstSearch 的第一个索引),那么就往下执行(5);就拿图1举个例子,假设 lstSearch 的第一个索引的元素是 A,然后删除 lstSearch 的第一个索引,那么 A 在 lstVertex 中的索引是不是0,这样就得到顶点列表 lstVertex 元素的索引0 。
(5)获取索引为 currIndex(currIndex同时也为lstVertex中元素的索引) 的顶点的第一个邻接顶点,并将这个邻接顶点的索引置为 nextIndex,然后往下执行(6)。
(6)如果 nextIndex 为-1,那么执行(3);如果 nextIndex 不为-1,那么往下执行(7)。
(7)如果 nextIndex 索引的顶点已经被访问,那么就获取索引为 currIndex 的下一个邻接顶点的索引,并把这个索引赋值给 nextIndex,然后执行(6);如果 nextIndex 索引的顶点未被访问,那么将索引为 nextIndex 的顶点加入到队列 lstSearch 里面,并将 isVisited[nextIndex] 置为 true,用于标识该 nextIndex 索引的顶点已经访问,将 nextIndex 作为索引从顶点列表 lstVertex 中输出顶点,然后获取索引为 currIndex 的下一个邻接顶点的索引,并把这个索引赋值给 nextIndex,然后执行(6)。
图1的广度遍历过程是这样的:
(1)从 A 顶点开始访问并把 A 顶点添加到队列 lstSearch 中,然后又从队列 lstSearch 中删除 A 顶点,再查找 A 顶点的所有邻接顶点 B、C 将其(B、C)输出并把 B、C 加入到队列 lstSearch 中。
(2)从 lstSearch 中删除 B 顶点并找完 B 顶点的所有邻接顶点 D、E 将其(D、E)输出,然后将顶点 D、E 添加到 lstSearch 中。
(3)从 lstSearch 中删除 C 顶点并找完 C 顶点的所有邻接顶点 F、G 将其(F、G)输出,然后将顶点 F、G 添加到 lstSearch 中。
(4)从 lstSearch 中删除 D 顶点并找完 D 顶点的所有邻接顶点 H 将其(H)输出,然后将顶点 H 添加到 lstSearch 中。
1、3 图的广度优先遍历代码实现
我们用代码实现一把;
(1)创建一个图类 MyChart :
package com.xiaoer.figure;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class MyChart {
/**
- 创建图
- @param vertexNum 顶点数量
- @return 返回 MyChart 对象
*/
public static MyChart createChart(int vertexNum) {
MyChart myChart = new MyChart(vertexNum);
myChart.addVertices();
myChart.addEdges();
return myChart;
}
/**
- 添加所有边,添加的是2个顶点直接相连的边,看图5, A与B是不是有一条边直接相连接,indexOf 方法返回的是传入的顶点
- 在一维数组的位置(也就是下标)
*/
private void addEdges() {
addEdge(indexOf("A"), indexOf("B"), 1);
addEdge(indexOf("A"), indexOf("C"), 1);
addEdge(indexOf("B"), indexOf("D"), 1);
addEdge(indexOf("B"), indexOf("E"), 1);
addEdge(indexOf("C"), indexOf("F"), 1);
addEdge(indexOf("C"), indexOf("G"), 1);
addEdge(indexOf("D"), indexOf("H"), 1);
addEdge(indexOf("E"), indexOf("H"), 1);
}
/**
- 添加所有顶点,看图5,是不是添加了A-H 这8个顶点
*/
private void addVertices() {
addVertex("A");
addVertex("B");
addVertex("C");
addVertex("D");
addVertex("E");
addVertex("F");
addVertex("G");
addVertex("H");
}
/**
- 顶点数量
*/
private int vertexNum;
/**
- 顶点列表
*/
private List
/**
- 顶点路径
*/
private int[][] vertexPathArray;
/**
- 边数量
*/
private int edgeNum;
/**
- 是否已经被访问
*/
private boolean[] isVisited;
MyChart(int vertexNum) {
this.vertexNum = vertexNum;
lstVertex = new ArrayList<>(vertexNum);
vertexPathArray = new int[vertexNum][vertexNum];
isVisited = new boolean[vertexNum];
}
/**
- 添加顶点, 此处不涉及扩容
- @param vertex
- 添加的顶点
*/
void addVertex(String vertex) {
if (vertexNum == lstVertex.size()) {
throw new ArrayIndexOutOfBoundsException("数组已满");
}
lstVertex.add(vertex);
}
/**
- 返回顶点所在的索引
- @param vertex 目标顶点
- @return 返回下标
*/
int indexOf(String vertex) {
return lstVertex.indexOf(vertex);
}
/**
- 添加边
- @param xIndex 横坐标
- @param yIndex 纵坐标
- @param weight 边的权重,weight = 1,表示2个顶点之间之间相连,就成了边
*/
void addEdge(int xIndex, int yIndex, int weight) {
if (xIndex >= vertexNum || yIndex >= vertexNum) {
throw new IndexOutOfBoundsException("索引越界");
}
vertexPathArray[xIndex][yIndex] = weight;
vertexPathArray[yIndex][xIndex] = weight;
edgeNum++;
}
/**
- 获取边数量
- @return 返回边数量
*/
int getEdgeNum() {
return edgeNum;
}
/**
- 获取下一个邻接节点
- @param index 当前顶点的横坐标
- @param nextIndex 当前顶点的前一个邻接顶点的纵坐标
- @return
*/
private int getNextNeighbor(int index, int nextIndex) {
/**
* i一开始为当前顶点的前一个邻接顶点的纵坐标+1算起,
* 举例:看图5,边的二维数组(vertexPathArr)为:
* A B C D E F G H
* A 0 1 1 0 0 0 0 0
* B 1 0 0 1 1 0 0 0
* C 1 0 0 0 0 1 1 0
* D 0 1 0 0 0 0 0 1
* E 0 1 0 0 0 0 0 1
* F 0 0 1 0 0 0 0 0
* G 0 0 1 0 0 0 0 0
* H 0 0 0 1 1 0 0 0
*
* 假设当前顶点为A顶点,已经查找完A顶点的第一个邻接顶点B,A所在的行坐标index就为0, B在第0行的第1列(nextIndex =
* 1)对不对,那么查找A的下一个邻接顶点是不是从 从第2列(nextIndex+1)开始查找
*/
for (int i = nextIndex + 1; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
return -1;
}
/**
- 获取第一个邻接节点
- @param index 表示行坐标
- @return
*/
private int getFirstNeighbor(int index) {
/**
* i一开始为0,表示从第0列开始查找
* 举例:看图5,边的二维数组(vertexPathArr)为:
* A B C D E F G H
* A 0 1 1 0 0 0 0 0
* B 1 0 0 1 1 0 0 0
* C 1 0 0 0 0 1 1 0
* D 0 1 0 0 0 0 0 1
* E 0 1 0 0 0 0 0 1
* F 0 0 1 0 0 0 0 0
* G 0 0 1 0 0 0 0 0
* H 0 0 0 1 1 0 0 0
*
* 假设当前顶点为C顶点,已经查找完C顶点的第一个邻接顶点A,C所在的行坐标index就为2,
* 那么查找C顶点的第一个邻接顶点是不是从第2行的第0列开始查找呢
*/
for (int i = 0; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
// 如果没有找到, 直接返回-1
return -1;
}
/**
- 广度优先遍历
*/
public void bfs() {
bfs(0);
}
private void bfs(int index) {
LinkedList lstSearch = new LinkedList<>();
System.out.print(lstVertex.get(index) + " -> ");
// 添加节点到集合
lstSearch.add(index);
// 标识节点为已经遍历
isVisited[index] = true;
// 队列不为空, 进行顺序处理
for (; lstSearch.size() > 0;) {
// 获取队列第一个顶点的索引
Integer currIndex = lstSearch.removeFirst();
// 获取顶点的邻接顶点的索引
int nextIndex = getFirstNeighbor(currIndex);
// 邻接节点存在
for (; -1 != nextIndex;) {
// 如果邻接顶点没有被访问过
if (!isVisited[nextIndex]) {
lstSearch.add(nextIndex);
isVisited[nextIndex] = true;
System.out.print(lstVertex.get(nextIndex) + " -> ");
}
// 获取下一个顶点的索引进行处理
nextIndex = getNextNeighbor(currIndex, nextIndex);
}
}
}
}
(2)创建一个测试类 Test :
package com.xiaoer.figure;
public class Test {
public static void main(String[] args) {
MyChart myChart = MyChart.createChart(8);
System.out.println("深度优先遍历: ");
myChart.bfs();
}
}
程序运行一下,日志打印如下所示;
图片