图的广度优先遍历(Java实现)

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 lstVertex;

/**

  • 顶点路径
    */

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();

}

}

程序运行一下,日志打印如下所示;

图片

你可能感兴趣的:(java)