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

文章收藏的好句子:这个世上,没有天上掉馅饼的灵感和等来的成就,只有日复一日的坚持和自律。

目录

1、图的广度优先遍历

     1、1 图的广度优先遍历基本思想

     1、2 图的广度优先遍历算法步骤

     1、3 图的广度优先遍历代码实现

1、图的广度优先遍历

1、1 图的广度优先遍历基本思想

图的表现形式第一篇(Java实现)这篇文章中,我们学习了图的深度优先遍历,这篇文章我们主要是学习一下图的广度优先遍历,这里我先介绍一下图的广度优先遍历基本思想,为了方便好理解这个思想,我先画一张图,如图1所示;

a99e6d3b4014b4ac0b9b35b64d4fb90f.png

它的思想:

(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个顶点没有直接相连接);

d51dfef7ca4e5e8497a8c87f6c14c24c.png

它的算法步骤(注意:算法步骤看不懂的话,先看下面的代码实现,再回过头来看算法步骤就好理解了):

(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<String> 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<Integer> 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();
  }


}

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

e5b059232d3b8a6b4ee45167cd84bce5.png

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值