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

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 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题进行加强训练了,那个时候简单题的意义就不大了,为了省一,加油。

相关文章
|
15天前
|
缓存 Java 应用服务中间件
Java虚拟线程探究与性能解析
本文主要介绍了阿里云在Java-虚拟-线程任务中的新进展和技术细节。
|
13天前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
23 11
|
8天前
|
域名解析 分布式计算 网络协议
java遍历hdfs路径信息,报错EOFException
java遍历hdfs路径信息,报错EOFException
21 3
|
6天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
19 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
13天前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
39 6
|
9天前
|
监控 算法 Java
深入解析Java中的垃圾回收机制
本文旨在全面解析Java的垃圾回收机制,探讨其工作原理、常见算法以及在实际开发中的应用。通过对这一重要主题的深入分析,希望帮助读者更好地理解Java虚拟机(JVM)如何管理内存,从而编写出更高效、稳定的Java应用程序。
|
9天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
|
19天前
|
Java 程序员 开发者
Java中的异常处理机制深度解析
本文旨在深入探讨Java中异常处理的核心概念与实际应用,通过剖析异常的本质、分类、捕获及处理方法,揭示其在程序设计中的关键作用。不同于常规摘要,本文将直接切入主题,以简明扼要的方式概述异常处理的重要性及其在Java编程中的应用策略,引导读者快速把握异常处理的精髓。
|
18天前
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
16 3
|
11天前
|
分布式计算 Java API
深入解析Java中的Lambda表达式及其应用
本文将深入探讨Java中Lambda表达式的定义、优势及其在实际编程中的应用。通过具体示例,帮助读者更好地理解和使用这一强大的编程工具。

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面