《深入理解经典广度优先遍历算法》

广度优先遍历:宽度优先遍历(Breadth-First Search, BFS), 图论和树论中基本的查找搜索算法, 是广大图算法的基础.。

前置知识和介绍

数据结构: 队列, 双端队列。 二叉树:经典bfs,按层bfs(即树的层序遍历)。
编程语言: Java

广度优先遍历的细节和可玩性很高。 广度优先遍历英文名缩写是bfs。 所以,大佬口中的bfs☞广度优先遍历。 二叉树,多叉树中的层序遍历也是广度优先遍历。

广度优先遍历是很多算法的原型。
比如拓扑排序的kahn算法, Prim最小生成树算法, 单源最短路径算法Dijsktra。

单单论广度优先遍历的变种:
朴素广度优先遍历 => 按层广度优先遍历.
多源广度优先遍历;
01BFS;
bfs与dfs结合生成路径;
bfs剪枝策略;
双向广搜;

本篇只介绍基础部分的内容

广度优先遍历

以下内容均用bfs代替广度优先遍历。
用途:

  • 遍历图中所有的顶点。
  • 遍历所有顶点的路径。
  • 无向不带权图, 或者带权图权重相同且权重为正值), 求两点之间的最短路径。
  • 图的连通性检验, 图连通分量求解
  • kahn,Prim,Dijsktra… 图算法都是bfs的变种实现。

下面是无向图的bfs
具体图解一下(平板上手写字有点丑, 对不起对不起对不起):
以如下图为例
bfs在于选择一个源节点开始扩散, 这里以f为例。

在这里插入图片描述
先将f的邻接点b,e,c,g遍历一遍。然后再对b,e,c,g邻接且为访问过的顶点进行遍历。

这里实现需要借助队列(先进先出的结构), 和哈希集合(用于记忆化搜索,避免重复遍历, 优化效率)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总而言之就是入队列是将这个点加入哈希集合中, 出队列就把它所有的未在哈希集合顶点加入到队列。
算法流程和代码实现在下文处有详解。

如果以最初的源头节点f标记为第一层,那么b,e,c,g就是第二层,a,d在第三层, 可以得到如下广度优先树。
在这里插入图片描述

这棵树是bfs在无向图中以源节点f的生成树。

这里简单给几个结论吧。

  1. 无向连通图中, 以某个节点扩散, 会得到以该节点为根节点的生成树(广度优先树)。
  2. 以src节点进行按层扩散, 朴素bfs不明显地区分层,但是按层的顺序。优化bfs(按层bfs)就把这个特征显示地展现了。=>这里类比经典二叉树层序遍历和优化层序遍历。
  3. 可以求解src到dest的最短边数, 比如,f->d点经过的最短边数是2。 需要处理f->d的所有简单路径然后挨个比大小吗? bfs当然不需要, 因为它总是处理完离源节点为k长度的所有节点,然后才处理k+1长度的节点。 bfs的性质。
  4. 前面提过,bfs可以解决最短路径问题。 不过这种最短路径的方法的使用条件必须记住。怎么说呢?毕竟bfs是相对"底层"的图算法, 最短路径算法有Bellman-Ford, A*, SPFA,Dijktra算法,它们的限制和使用条件也不尽相同。 我想说bfs使用限制应该相较于上述的最短路径算法要更加局限。
    使用条件:1. 无权图:这种情况等价于3求最短边问题。 2:带权图: a->b且b->a的相互权重相同, 且权重不为负值,不需要所有边权重都统一。

说了这么多, 你可能会吐槽, “我怎么记得住呢?” 下面先介绍算法流程,代码实现,然后我会以bfs求解图中连通分量问题, 无向图判环, 有向图判环来辅助理解这些性质

广度优先遍历的基本实现

这里重申一遍上面用过的容器

  1. 需要队列这种数据结构辅助。
    由于BFS需要由近到远的处理, 队列这种先进先出的结构可以总是让距离近的顶点先被处理。 因此广度优先遍历需要队列存储顶点和维护顺序。
  2. 需要哈希表(哈希集合)来标记已访问节点。
    BFS适用于无向图和带环图, 必须标记已访问的节点, 否则可能会陷入死循环重复处理同一节点或者困于环中。有个名词,记忆化搜索, 就是这个。

以下是朴素广度优先遍历的Java实现

算法流程:

  1. 给定一个源节点s, 将其加入队列, 并用哈希表标记开始遍历。
  2. 弹出对头的节点,并扫描它的所有关联的顶点, 若当前关联顶点未标记过那么加入队列。
  3. 重复2过程直至队列为空, 弹出对头节点时可以进行额外处理(比如打印,收集…)。

三种建图方式实现
任选一种你喜欢的方式即可。

链式前向星

package BFS;


public class Code01_BFS {
   
    //链式前向星的bfs写法
    public static int MAXN = 1001;
    public static int MAXM = 2003;
    public static int[] head = new int[MAXN];
    public static int[] next = new int[MAXM];
    public static int[] to = new int[MAXM];
    public static int cnt;
    public static int[] queue = new int[MAXN];
    public static int l,r;
    public static boolean[] visited = new boolean[MAXN];
    //把0编号空出来
    public static void build(int n){
   
        for(int i=1;i<=n;i++){
   
            head[i] = 0;
            visited[i] = false;
        }
        cnt = 1;
    }
    //添加一条有向边u->v
    //无向边就交换参数多调一次这个。
    public static void addEdge(int u,int v){
   
        next[cnt] = head[u];
        to[cnt] = v;
        head[u] = cnt++;
    }
    public static void bfs(int start){
   
        if(start == 0) {
   
            return;
        }
        queue[r++] = start;
        visited[start] = true;
        while(l != r){
   
            int cur = queue[l++];
            //这里可以处理cur...打印,收集,处理信息...
            System.out.print(cur+" ");
            for(int ei = head[cur]; ei != 0;ei = next[ei]){
   
                int dst = to[ei];
                if(!visited[dst]){
   
                    queue[r++] = dst;
                    visited[dst] = true;//标记已访问过
                }
            }
        }
    }
    // 主函数
    public static void main(String[] args) {
   
        build(6); // 假设图有6个节点
        //这种图结构
        //1 -> 2, 3
        //2 -> 4, 5
        //3 -> 6
        addEdge(1, 2);
        addEdge(1, 3);
        addEdge(2, 4);
        addEdge(2, 5);
        addEdge(3, 6);

        System.out.println("BFS traversal starting from node 1:");
        bfs(1); // 从节点1开始BFS遍历
    }
}

邻接矩阵:

package BFS;

public class Code02_BFS_Matrix {
   
    public static int MAXN = 101;
    public static int[][] graph = new int[MAXN][MAXN];
    public static int[] queue = new int[MAXN];
    public static int l,r;
    public static boolean[] visited = new boolean[MAXN];
    public static int n;
    public static void build(){
   
        for(int i=1;i<=n;i++){
   
            for(int j=1;j<=n;j++){
   
                graph[i][j] = 0;
            }
            visited[i] = false;
        }
    }
    //添加有向不带权边。u->v
    //无向边交换参数又调一次。
    public static void addEdge(int u,int v){
   
        graph[u][v] = 1;
    }
    public static void bfs(int start){
   
        if(start == 0) {
   
            return
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值