数据结构第26节 广度优先搜索

广度优先搜索(Breadth-First Search,简称BFS)是一种用于遍历或搜索树或图数据结构的算法。其主要特性是以层级顺序遍历图的所有节点,从一个指定的起点开始,首先访问所有直接相连的邻居节点,然后再访问它们的邻居,以此类推,直至遍历完所有的可达节点。

BFS的工作原理:

  1. 选择起点:BFS从一个指定的起点开始。

  2. 初始化队列:创建一个空队列,并将起点插入队列中。

  3. 标记节点:起点被标记为“已访问”,以避免重复访问。

  4. 遍历队列:从队列中取出第一个节点,并访问它所有未被访问过的邻居节点。将这些邻居节点标记为“已访问”并插入队列的末尾。

  5. 重复过程:继续从队列中取出下一个节点,重复步骤4,直到队列为空。

  6. 终止条件:当队列为空时,算法结束,此时所有可达的节点都被遍历过了。

时间复杂度:

BFS的时间复杂度通常是O(V + E),其中V是图中的顶点数,E是边的数量。这是因为每个顶点和每条边都会被访问一次。

空间复杂度:

BFS的空间复杂度取决于队列的最大大小,最坏情况下,如果所有的节点都在队列中,则空间复杂度为O(V)。

应用场景:

  • 最短路径:在无权图或权重相等的图中寻找两个节点之间的最短路径。
  • 连通性:确定图是否连通,以及找出连通分量。
  • 层次遍历:在树形结构中,按层次顺序访问节点。
  • 拓扑排序:在有向无环图中进行排序。
  • 迷宫求解:寻找从起点到终点的路径。
  • 社交网络分析:例如计算用户之间的距离或找到共同朋友。

实现示例:

下面是一个使用Python实现的BFS算法示例,用于在图中寻找两个顶点之间的路径:

from collections import dequedef bfs(graph, start, goal):# 创建一个队列并插入起点queue = deque([start])# 创建一个字典来存储每个节点的父节点parents = {start: None}while queue:current = queue.popleft()if current == goal:# 目标节点已找到,构建并返回路径path = []while current is not None:path.append(current)current = parents[current]return list(reversed(path))# 遍历当前节点的所有邻居for neighbor in graph[current]:if neighbor not in parents:# 标记邻居节点,并将其父节点设为当前节点parents[neighbor] = currentqueue.append(neighbor)# 如果没有找到目标节点return None

在上述示例中,graph是一个邻接列表,表示图的结构,startgoal分别表示起点和终点。该函数返回从startgoal的路径,如果没有找到路径则返回None

广度优先搜索(Breadth-First Search,简称BFS)是一种用于遍历或搜索树或图数据结构的算法。在游戏开发中,BFS经常用来解决诸如寻路、迷宫求解、计算连通性、寻找最近的敌人或资源点等问题。

下面我将以一个典型的迷宫游戏作为案例,来详细讲解如何在Java中实现广度优先搜索。

游戏案例:迷宫寻路

假设我们有一个二维迷宫,由一系列的格子组成,每个格子可以是墙或者空地。玩家需要从起点找到到达终点的最短路径。我们可以使用BFS来解决这个问题。

1. 定义迷宫

首先,我们需要定义迷宫的结构。我们可以用一个二维数组来表示迷宫,其中0代表可通行的空地,1代表墙。

int[][] maze = {{1, 1, 1, 1, 1, 1, 1},{1, 0, 0, 0, 0, 0, 1},{1, 0, 1, 1, 1, 0, 1},{1, 0, 0, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1}
};
2. 定义节点类

为了存储迷宫中的每个位置状态,我们需要创建一个节点类,包含位置坐标和从起点到该位置的距离。

class Node {int x;int y;int distance;Node(int x, int y, int distance) {this.x = x;this.y = y;this.distance = distance;}
}
3. 实现BFS

接下来,我们实现BFS算法。我们将使用队列来存储待访问的节点,并且维护一个布尔型的二维数组来标记已经访问过的节点。

public static boolean bfs(int[][] maze, int startX, int startY, int endX, int endY) {// 队列用于存储待访问的节点Queue<Node> queue = new LinkedList<>();// 标记哪些节点已经被访问过boolean[][] visited = new boolean[maze.length][maze[0].length];// 将起点加入队列并标记为已访问queue.add(new Node(startX, startY, 0));visited[startX][startY] = true;// 方向数组,用于四个方向的移动int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};while (!queue.isEmpty()) {Node current = queue.poll();// 如果找到了终点,返回trueif (current.x == endX && current.y == endY) {System.out.println("Shortest distance: " + current.distance);return true;}// 检查四个方向for (int[] dir : directions) {int newX = current.x + dir[0];int newY = current.y + dir[1];// 检查新位置是否在迷宫内并且未被访问过if (newX >= 0 && newX < maze.length && newY >= 0 && newY < maze[0].length &&maze[newX][newY] == 0 && !visited[newX][newY]) {// 将新位置加入队列并标记为已访问queue.add(new Node(newX, newY, current.distance + 1));visited[newX][newY] = true;}}}// 如果没有找到路径,返回falsereturn false;
}
4. 调用BFS

最后,我们可以在主函数中调用bfs方法,传入迷宫和起点、终点的坐标。

public static void main(String[] args) {int[][] maze = {// 迷宫数组};boolean found = bfs(maze, 1, 1, maze.length - 2, maze[0].length - 2);if (!found) {System.out.println("No path found.");}
}

这个算法可以确保找到从起点到终点的最短路径,如果存在多条最短路径,它会返回其中任意一条。

让我们将上述概念转化为一个完整的Java程序。我们将添加一些额外的功能,比如输出路径,以及处理没有找到路径的情况。以下是一个完整的示例代码:

import java.util.LinkedList;
import java.util.Queue;public class MazeSolver {private static final int WALL = 1;private static final int PATH = 0;public static void main(String[] args) {int[][] maze = {{1, 1, 1, 1, 1, 1, 1},{1, 0, 0, 0, 0, 0, 1},{1, 0, 1, 1, 1, 0, 1},{1, 0, 0, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1}};int startX = 1, startY = 1;int endX = maze.length - 2, endY = maze[0].length - 2;if (bfs(maze, startX, startY, endX, endY)) {printPath(maze, startX, startY, endX, endY);} else {System.out.println("No path found.");}}public static boolean bfs(int[][] maze, int startX, int startY, int endX, int endY) {Queue<Node> queue = new LinkedList<>();boolean[][] visited = new boolean[maze.length][maze[0].length];queue.add(new Node(startX, startY, 0));visited[startX][startY] = true;int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};while (!queue.isEmpty()) {Node current = queue.poll();if (current.x == endX && current.y == endY) {setPath(maze, current);return true;}for (int[] dir : directions) {int newX = current.x + dir[0];int newY = current.y + dir[1];if (isValidMove(maze, newX, newY, visited)) {queue.add(new Node(newX, newY, current.distance + 1));visited[newX][newY] = true;// Store the previous node to reconstruct the path latermaze[newX][newY] = current.distance + 1;}}}return false;}public static boolean isValidMove(int[][] maze, int x, int y, boolean[][] visited) {return x >= 0 && x < maze.length && y >= 0 && y < maze[0].length &&maze[x][y] == PATH && !visited[x][y];}public static void setPath(int[][] maze, Node endNode) {int x = endNode.x;int y = endNode.y;while (maze[x][y] != 0) {maze[x][y] = 2; // Mark the path with 2int prevDist = maze[x][y] - 1;for (int i = -1; i <= 1; i++) {for (int j = -1; j <= 1; j++) {if (i == 0 || j == 0) {int newX = x + i;int newY = y + j;if (newX >= 0 && newX < maze.length && newY >= 0 && newY < maze[0].length &&maze[newX][newY] == prevDist) {x = newX;y = newY;break;}}}}}}public static void printPath(int[][] maze, int startX, int startY, int endX, int endY) {System.out.println("Path from (" + startX + ", " + startY + ") to (" + endX + ", " + endY + "):");for (int[] row : maze) {for (int cell : row) {switch (cell) {case WALL:System.out.print("# ");break;case PATH:System.out.print(". ");break;case 2:System.out.print("* ");break;default:System.out.print("S ");}}System.out.println();}}static class Node {int x;int y;int distance;Node(int x, int y, int distance) {this.x = x;this.y = y;this.distance = distance;}}
}

在这个版本中,我们增加了以下功能:

  1. setPath 方法用于在找到路径后,通过回溯从终点到起点的节点距离来标记出整个路径。
  2. printPath 方法用于打印迷宫,包括起点、终点和找到的路径。
  3. isValidMove 方法用于检查下一步移动是否有效,即不超出迷宫边界、不是墙壁、且尚未访问过。

这样,我们不仅能够找出从起点到终点的最短路径,还可以在迷宫中可视化这条路径。

为了进一步完善代码,我们可以做以下几点改进:

  1. 错误处理:增加对输入参数的检查,确保迷宫和起始/结束点的有效性。
  2. 性能优化:在bfs中,我们使用距离来追踪路径,这在小规模迷宫中可行,但在大规模迷宫中可能占用大量内存。我们可以改用前驱节点的方式,这样只需存储每个节点的前一个节点即可。
  3. 代码清晰性:分离关注点,比如将路径设置逻辑独立出来。

下面是根据这些改进后的代码:

import java.util.LinkedList;
import java.util.Queue;public class MazeSolver {private static final int WALL = 1;private static final int PATH = 0;private static final int START = 3;private static final int PATH_MARK = 2;public static void main(String[] args) {int[][] maze = {{1, 1, 1, 1, 1, 1, 1},{1, 0, 0, 0, 0, 0, 1},{1, 0, 1, 1, 1, 0, 1},{1, 0, 0, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1}};int startX = 1, startY = 1;int endX = maze.length - 2, endY = maze[0].length - 2;if (validateMaze(maze, startX, startY, endX, endY)) {if (bfs(maze, startX, startY, endX, endY)) {printPath(maze, startX, startY, endX, endY);} else {System.out.println("No path found.");}} else {System.out.println("Invalid maze or start/end points.");}}private static boolean validateMaze(int[][] maze, int startX, int startY, int endX, int endY) {return startX >= 0 && startY >= 0 && endX >= 0 && endY >= 0 &&startX < maze.length && startY < maze[0].length &&endX < maze.length && endY < maze[0].length &&maze[startX][startY] == PATH && maze[endX][endY] == PATH;}public static boolean bfs(int[][] maze, int startX, int startY, int endX, int endY) {Queue<Node> queue = new LinkedList<>();boolean[][] visited = new boolean[maze.length][maze[0].length];int[][] predecessors = new int[maze.length][maze[0].length];for (int[] row : predecessors) {java.util.Arrays.fill(row, -1);}queue.add(new Node(startX, startY, null));visited[startX][startY] = true;int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};while (!queue.isEmpty()) {Node current = queue.poll();if (current.x == endX && current.y == endY) {setPath(predecessors, current);return true;}for (int[] dir : directions) {int newX = current.x + dir[0];int newY = current.y + dir[1];if (isValidMove(maze, newX, newY, visited)) {queue.add(new Node(newX, newY, current));visited[newX][newY] = true;predecessors[newX][newY] = current.distance;}}}return false;}public static boolean isValidMove(int[][] maze, int x, int y, boolean[][] visited) {return x >= 0 && x < maze.length && y >= 0 && y < maze[0].length &&maze[x][y] == PATH && !visited[x][y];}public static void setPath(int[][] predecessors, Node endNode) {int x = endNode.x;int y = endNode.y;while (predecessors[x][y] != -1) {Node prev = predecessors[x][y];maze[x][y] = PATH_MARK; // Mark the path with 2x = prev.x;y = prev.y;}maze[x][y] = START; // Mark the start point}public static void printPath(int[][] maze, int startX, int startY, int endX, int endY) {System.out.println("Path from (" + startX + ", " + startY + ") to (" + endX + ", " + endY + "):");for (int[] row : maze) {for (int cell : row) {switch (cell) {case WALL:System.out.print("# ");break;case PATH:System.out.print(". ");break;case PATH_MARK:System.out.print("* ");break;case START:System.out.print("S ");break;default:System.out.print(" ");}}System.out.println();}}static class Node {int x;int y;Node distance;Node(int x, int y, Node distance) {this.x = x;this.y = y;this.distance = distance;}}
}

在这个版本中,我们进行了以下改进:

  • 使用predecessors数组来存储每个节点的前驱节点,而不是其距离。
  • setPath方法中,我们通过遍历predecessors数组来重构路径。
  • 添加了validateMaze方法来确保迷宫和起始/结束点的有效性。

这样,我们的代码现在更加健壮,更易于理解和维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/46117.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧商超-下

原文&#xff1a;https://blog.c12th.cn/archives/29.html 智慧商超-下 测试&#xff1a;笔记本原装操作系统&#xff1a;Windows 10 家庭中文版 资源分享链接&#xff1a;提取码&#xff1a;uya0 卸载SQL步骤&#xff1a; https://blog.csdn.net/qq1623803207/article/detail…

Linux yum-config-manager命令教程:轻松管理YUM软件源(附实例详解和注意事项)

Linux yum-config-manager命令介绍 yum-config-manager 是一个用来管理 YUM 软件源的工具&#xff0c;它允许用户启用、禁用、添加或删除软件源。这个命令对于 Linux 系统管理员来说非常重要&#xff0c;因为它可以帮助他们控制软件包的来源&#xff0c;确保系统安装的软件是最…

自定义json序列化和反序列化

一、LocalDateTime反序列化异常 首先我们定义一个java POJO实体类&#xff0c;其中关键的成员变量时birthDate,我们没有采用Date数据类型&#xff0c;而是采用了Java8 新的日期类型LocalDateTime,使用LocalDateTime的好处我就不多说了&#xff0c;有很多的文章解释说明。我们把…

Java常见JUC并发工具类

Lock 并发编程领域的两大核心问题&#xff1a; 一个是 互斥&#xff0c;即同一时刻只允许一个线程访问共享资源 另一个是 同步&#xff0c;即线程之间如何通信、协作 这两大问题&#xff0c;管程&#xff08;synchronized&#xff09;都是能够解决的。Java SDK并发包通过Lock和…

详解一下马拉车算法 Manache算法 使用c++

马拉车算法是寻找最长回文子串的高效算法&#xff0c;时间复杂度为O&#xff08;n&#xff09; #include <iostream> #include <string> #include <vector> using namespace std;string longestPalindrome(string s) {// 步骤1: 预处理&#xff0c;在字符间…

【Linux】进程控制的详细介绍

前言 在此之前&#xff0c;我们学过进程的概念&#xff0c;进程的状态&#xff0c;进程地址空间等一系列进程相关的问题。本章我们继续学习进程&#xff0c;我们要来学习一下进程的控制&#xff0c;关于进程等待&#xff0c;进程替换等问题。 目录 1.再次认识Fork函数1.1 fork…

internet download manager(IDM下载器) 6.42.8.2下载安装使用指南

internet download manager(IDM下载器) 6.42.8.2Z是一款功能强大的下载加速工具&#xff0c;能够显著提升您的下载速度&#xff0c;最高可达500%。它不仅能够加速下载&#xff0c;还能对下载任务进行智能调度&#xff0c;并具备恢复中断下载的能力。根据用户评价&#xff0c;无…

初识C++(命名空间、缺省参数)

初识C 命名空间namespace关键字命名空间的使用 缺省参数 命名空间 namespace关键字 在C中&#xff0c;为了尽可能避免命名冲突&#xff0c;需要对各个变量进行域作用限定&#xff0c;这就需要使用到namespace关键字&#xff0c;namespace可以定义一个命名空间&#xff0c;即命…

LabVIEW红外热波图像缺陷检

开发使用LabVIEW开发的红外热波图像缺陷检测系统。该系统结合红外热像仪、工业相机和高效的数据采集硬件&#xff0c;实现对工件表面缺陷的自动检测和分析。通过LabVIEW的强大功能&#xff0c;系统能够实时采集、处理和显示红外热波图像&#xff0c;有效提高了检测的精度和效率…

vue:标签属性绑定Vue实例【ref,reactive,内置指令v-bind,v-on】,预定义变量、方法【$methods,$computed】

Vue2、3组件通信、双向绑定、插槽slot、内置指令_组件双向绑定-CSDN博客​Vue2&#xff0c;3响应式原理&#xff0c;ref和reactive&#xff0c;toRef和toRefs&#xff0c;shallowRef和shallowRefs_vue2 shallowref-CSDN博客 vue2【Options 选项API、mixin混入】&#xff0c;vu…

WAF基础介绍

WAF 一、WAF是什么&#xff1f;WAF能够做什么 二 waf的部署三、WAF的工作原理 一、WAF是什么&#xff1f; WAF的全称是&#xff08;Web Application Firewall&#xff09;即Web应用防火墙&#xff0c;简称WAF。 国际上公认的一种说法是&#xff1a;Web应用防火墙是通过执行一…

免开steam 脱离steam 进行游戏的小工具

链接&#xff1a;https://pan.baidu.com/s/1k2C8b4jEqKIGLtLZp8YCgA?pwd6666 提取码&#xff1a;6666 我们只需选择游戏根目录 然后输入AppID 点击底部按钮 进行就可以了 关于AppID在&#xff1a;

机器学习——L1 L2 范数 —>L1 L2正则化

1、L1范数和L2范数是机器学习和数据分析中经常使用的两种范数&#xff0c;它们之间存在多个方面的区别。 以下是关于L1范数和L2范数区别的详细解释&#xff1a; 一、定义差异 L1范数&#xff1a;也被称为曼哈顿范数&#xff0c;是向量元素的绝对值之和。对于一个n维向量x&am…

酒店管理系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;酒店管理员管理&#xff0c;房间类型管理&#xff0c;房间信息管理&#xff0c;订单信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;房间信息…

Linux介绍与常用命令详解

目录 一、Linux概述 1.Linux发行版 2.Linux目录结构 二、Linux特点 三、Linux用途 四、Linux常用的命令 1.cd指令&#xff08;跳转位置&#xff09; 2.显示目录文件 3.对文件进行操作 4.rm指令&#xff08;删除文件夹指令&#xff09; 5.mv指令 6.查看文件命令 7.进程命令…

【云岚到家】-day05-6-项目迁移-门户-CMS

【云岚到家】-day05-6-项目迁移-门户-CMS 4 项目迁移-门户4.1 迁移目标4.2 能力基础4.2.1 缓存方案设计与应用能力4.2.2 静态化技术应用能力 4.3 需求分析4.3.1 界面原型 4.4 系统设计4.4.1 表设计4.4.2 接口与方案4.4.2.1 首页信息查询接口4.4.3.1 数据缓存方案4.4.3.2 页面静…

力扣678.有效的括号字符串

力扣678.有效的括号字符串 用两个栈分别存’ ( ‘和‘ * ’的下标 ‘ ) ’ 与二者匹配最后将‘ ( ’与 ‘ * ’匹配 class Solution {public:bool checkValidString(string s) {stack<int> st1,st2;int n s.size();for(int i0;i<n;i){char c s[i];if(c ()st1.pus…

宪法学学习笔记(个人向) Part.5

宪法学学习笔记(个人向) Part.5 4. 公民基本权利和义务 4.1 公民&#x1f338; 概念 是指具有某个国家国籍的自然人&#xff1b; 【拓展】国籍&#xff1a;在宪法上是指一个人隶属于某个国家的法律上的身份&#x1f338; &#xff1b; 取得方式 出生国籍 因出生而获得的国籍&a…

Ubuntu20.04 编译安装FFmpeg,出错分析以及解决方案

最近工程上需要对FFmpeg底层源码进行修改&#xff0c;需要重新编译&#xff0c;遇见不少坑&#xff0c;出篇教程记录一下。 文章目录 1.FFmpeg源码下载地址2.编译环境配置3.编译FFmpeg4.配置FFmpeg运行环境 1.FFmpeg源码下载地址 官方下载地址:Index of /releases (ffmpeg.or…

Java | Leetcode Java题解之第232题用栈实现队列

题目&#xff1a; 题解&#xff1a; class MyQueue {Deque<Integer> inStack;Deque<Integer> outStack;public MyQueue() {inStack new ArrayDeque<Integer>();outStack new ArrayDeque<Integer>();}public void push(int x) {inStack.push(x);}pub…