BFS(广度搜索|宽度搜索)无向图遍历(JAVA手把手深入解析)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: BFS(广度搜索|宽度搜索)无向图遍历(JAVA手把手深入解析)

BFS(广度搜索|宽度搜索)无向图遍历(JAVA手把手深入解析)


前言

       到了DFSBFS这里就是一个省一的分界线了,能搞定的省一基本没有问题,当然,也有靠纯暴力进入省一的,但是几率就会小一些。这篇文章我已经将BFS拆分的很细了呢,希望能帮助大家跨过蓝桥杯的这个分水岭。

       如果帮助到了你,请留下你的三连支持。

BFS广度搜索

       宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止

这里与DFS就有一定的区别了,他的运转方式就是横向走遍所有的节点,虽然都是从上到下,但是横向的BFS是横向挨个找,一般会使用队列来完成BFS操作。

由于DFS的代码理解难度小一些,我先准备了DFS的文章,可以先去完成DFS学习之后咱们再来完成BFS的学习,有一个从简入繁的过程:

DFS无向图遍历(JAVA手把手深入解析)_红目香薰的博客-CSDN博客

无向图

BFS与DFS的区别通过图就很明显了,而且上面我还配了一张GIF动图,相信更容易理解了,我们通过这个图再翻译成数组。

我们用BFS输出应该是:1-2-6-3-4-7-5,我们编码完毕后测试一下。

BFS全局变量定义

1、节点

为了帮助孩子们理解,我这里使用的是拼音拼写的变量【dian】

public static String dian[] = { "1", "2", "3", "4", "5", "6", "7" };

2、节点数

我们所有的操作都会依赖于这个长度来进行遍历,故而这里我单独写了一下:

public static int d_length=d.length;

3、根据图创建数组

这里的创建数组图的方式与DFS是相同的,咱们图一样只是遍历的方式不同而已。我就不做其它解释了。

/**
   * 图转换数组
   */
  public static int[][] arr= {
      {0,1,0,0,0,1,0},
      {1,0,1,1,0,0,0},
      {0,1,0,0,0,0,0},
      {0,1,0,0,1,0,0},
      {0,0,0,1,0,0,0},
      {1,0,0,0,0,0,1},
      {0,0,0,0,0,1,0},
  };

4、状态记录数组

/**
* 记录每一个节点的遍历过程,走过则记录为true
*/
public static boolean[] isfStatus;

四个全局变量

这里我们共计创建了4个全局变量,依次是:

顶点、图转换数组、判断是否走过、记录每一个节点的遍历过程,走过则记录为true。

/**
   * 顶点
   */
  public static int d[] = { 1, 2, 3, 4, 5, 6, 7 };
  /**
   * 图转换数组
   */
  public static int[][] arr= {
      {0,1,0,0,0,1,0},
      {1,0,1,1,0,0,0},
      {0,1,0,0,0,0,0},
      {0,1,0,0,1,0,0},
      {0,0,0,1,0,0,0},
      {1,0,0,0,0,0,1},
      {0,0,0,0,0,1,0},
  };
  /**
   * 顶点长度
   */
  public static int d_length=d.length;
  /**
   * 记录每一个节点的遍历过程,走过则记录为true
   */
  public static boolean[] isfStatus;

这里准备的东西其实与DFS是相同的,包括图的数组绘制理论,接下来才是真正的广搜代码内容。

BFS代码

1、队列解析

这里我们要完成BFS则需要使用队列,Java中队列会使用【Queue】来完成,这个【Queue】在【LinkedList】内,我们声明的时候直接使用:

Queue<Integer> temp = new LinkedList<Integer>();

来声明这个队列即可。

向队末添加元素是【offer】函数,取出第一个元素并删除是【poll】函数,我们利用队列的这两个函数就够用了。

2、广搜核心代码

广搜我们就不需要递归了,相对理解难度在于多层循环这里。我们先看一下逻辑源码:

/**
     * 队列完成的广度搜索
     */
    private static void BFS() {
      isfStatus = new boolean[d_length];
        Queue<Integer> temp = new LinkedList<Integer>();
        //遍历每个节点
        for (int i = 0; i < d_length; i++) {
          //判断当前节点是否被访问过
            if (!isfStatus[i]) {//如果没有被访问的话则将其加入队列
                System.out.print(d[i] + " ");
                isfStatus[i] = true;
                temp.offer(i);
                while (!temp.isEmpty()) {
                  //将最先进入队列的节点移除
                    int j = temp.poll();
                    //广度搜索子节点
                    for (int k = firstValue(j); k >= 0; k = nextValue(j, k)) {
                        if (!isfStatus[k]) {
                            System.out.print(d[k] + " ");
                            isfStatus[k] = true;
                            temp.offer(k);
                        }
                    }
                }
            }
        }
    }

从逻辑中可以看出,首先我们还是要遍历整个节点长度,然后如果是非true就代表没有走过的路线,可以进入判断,先直接输出一下这个点,因为我们图是按照从小到大排列的故而不需要考虑点的排序。

这个需要进行队列操作了,进来循环后先排队,先一处节点后再进行广度搜索的子节点添加。当然,同时走过的路线需要修改一下状态的数组下标为true。遍历范围还是从第一个点遍历到最后一个点。

3、遍历节点

理论与DFS相同

全局控制:变量【i】,我们通过变量【i】来控制我们遍历的行数,这样就能逐一击破了。

初始点:坐标点需要从最左侧的0开始遍历,只要找到不是0的数就代表有链接点了。

下一个链接:坐标得从第N个开始遍历了,因为之前的已经遍历过了。这里的N是变量【j】。

// 返回与i相连的第一个顶点
    private static int firstValue(int i) {
        for (int j = 0; j < d_length; j++) {
            if (arr[i][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    // 返回与i相连的下一个顶点
    private static int nextValue(int i, int j) {
        for (int k = (j + 1); k < d_length; k++) {
            if (arr[i][k] > 0) {
                return k;
            }
        }
        return -1;
    }

4、最终输出

// 测试
    public static void main(String args[]) {
        BFS();
    }

可以看到最终结果与深度搜索的目标结果是相同的,代表我们遍历是没有问题的。

完整代码对照

package com.item.action;
import java.util.LinkedList;
import java.util.Queue;
 
public class Demo5 {
  /**
   * 顶点
   */
  public static int d[] = { 1, 2, 3, 4, 5, 6, 7 };
  /**
   * 图转换数组
   */
  public static int[][] arr= {
      {0,1,0,0,0,1,0},
      {1,0,1,1,0,0,0},
      {0,1,0,0,0,0,0},
      {0,1,0,0,1,0,0},
      {0,0,0,1,0,0,0},
      {1,0,0,0,0,0,1},
      {0,0,0,0,0,1,0},
  };
  /**
   * 顶点长度
   */
  public static int d_length=d.length;
  /**
   * 记录每一个节点的遍历过程,走过则记录为true
   */
  public static boolean[] isfStatus;
  
    /**
          * 队列完成的广度搜索
     */
    private static void BFS() {
      isfStatus = new boolean[d_length];
        Queue<Integer> temp = new LinkedList<Integer>();
        //遍历每个节点
        for (int i = 0; i < d_length; i++) {
          //判断当前节点是否被访问过
            if (!isfStatus[i]) {//如果没有被访问的话则将其加入队列
                System.out.print(d[i] + " ");
                isfStatus[i] = true;
                temp.offer(i);
                while (!temp.isEmpty()) {
                  //将最先进入队列的节点移除
                    int j = temp.poll();
                    //广度搜索子节点
                    for (int k = firstValue(j); k >= 0; k = nextValue(j, k)) {
                        if (!isfStatus[k]) {
                            System.out.print(d[k] + " ");
                            isfStatus[k] = true;
                            temp.offer(k);
                        }
                    }
                }
            }
        }
    }
    // 返回与i相连的第一个顶点
    private static int firstValue(int i) {
        for (int j = 0; j < d_length; j++) {
            if (arr[i][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    // 返回与i相连的下一个顶点
    private static int nextValue(int i, int j) {
        for (int k = (j + 1); k < d_length; k++) {
            if (arr[i][k] > 0) {
                return k;
            }
        }
        return -1;
    }
 
    // 测试
    public static void main(String args[]) {
        BFS();
    }
}

完整代码留在这里是帮助大家对照的啊,别直接复制用,这样意义就不那么高了。

总结

       我们咋做蓝桥杯题的时候很多的时候都是有套路的,我们很多时候通过我们背过的一些套路去套题目也会直接出结果的,例如全排列的方法,还有很多的公式,欧几里得,欧拉等等,都是很方便的,我们其实不是算法的创造者,我们只是知识的搬运工,争取将更多的知识搬运到咱们的大脑中啊。

连续的两篇文章我们对DFS于BFS都做了一定的理解,有了这个基础我们才能更好的应对各省的省赛题目,当然,最难的题目应该还是DP类型题,所以后面我们还是需要更好的去读题,理解题,用量来积累,千万别看到难题就跑,我们可以先不搭理它,先去解决一些简单的题目,通过在简单题目中累积的解题思路来一点点摸索出解题规律,解题技巧。

3月份前没有DP分享了,3月份的时候请将各省的省赛题的前6~7题做一遍,简单的先搞定了再说难的8~10题,整个3月份就会针对8~10题进行加强训练了,那个时候简单题的意义就不大了,为了省一,加油。

相关文章
|
3天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
45 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
10天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
8天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
1月前
|
Java 编译器
Java 泛型详细解析
本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
47 2
Java 泛型详细解析
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
30天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
30天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
1月前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
2月前
|
数据采集 存储 Web App开发
Java爬虫:深入解析商品详情的利器
在数字化时代,信息处理能力成为企业竞争的关键。本文探讨如何利用Java编写高效、准确的商品详情爬虫,涵盖爬虫技术概述、Java爬虫优势、开发步骤、法律法规遵守及数据处理分析等内容,助力电商领域市场趋势把握与决策支持。
|
2月前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####

热门文章

最新文章