• 广度优先搜索BFS:图与迷宫


    目录

    一、BFS定义

    二、BFS与无权图的最短路问题

    三、BFS与迷宫问题

    四、BFS与连通块问题


    一、BFS定义

    我们以图的搜索来引入广度优先搜索的定义。如下是一张无向图,现对其进行广度优先遍历:对于每一结点,广度优先搜索会优先遍历该结点所有可能的分支,如对于A,它的分支有B与F,则接下来就将遍历B与F,后续所有结点同理。若以A为起点,则该图一种可能的广度优先搜索结果是:A BF CIGE DH。

    9c5cdfe3f5be474d8696b58c1b0be9cf.png

    其具体实现可以利用队列完成:

    若起点为A:

    将起点A放入队列;        q:A

    取出A,A能扩展出B、F,BF入队;        q:BF

            取出B,B能扩展出C、I、G,CIG入队;        q:FCIG

            取出F,F能扩展出E,E入队;         q:CIGE

                    取出C,C能扩展出D,D入队;        q:IGED

                    取出I,I能扩展出的结点此前已经被扩展过,无需重复入队;        q:GED

                    取出G,G能扩展出H,H入队;        q:EDH

                    取出E,E能扩展出的结点此前已经被扩展过,无需重复入队;        q:DH

                            取出D,D能扩展出的结点此前已经被扩展过,无需重复入队;        q:H

                            取出H,q为空,遍历完成;

    遍历结果为:A BF CIGE DH。

    二、BFS与无权图的最短路问题

    BFS有什么优势呢?那就是将整幅图划分出了层次,第一层:A、第二层:BF、第三层:CIGE、第四层:DH,所以广搜又可以叫做层次遍历。广搜可以用来求得无权图上任意两结点间的最短路,其就等于它们分别所在的层数之差的绝对值,如结点B到H的最短路为 4-2=2。(默认无权图的所有边权都等于1,如果图并非是无权图,但其边权均相等为n,也同样能用bfs求解,最后的结果乘以n即可)

    这是一个十分重要的应用,很多问题都可以转化为无权图的最短路求解,如下面两道例题:

    例1:抓住那头牛

    我们以农夫位于2,牛位于7为例,农夫有三种移动方式,我们将其以树的形式表示(树是一种特殊的图)如下:

    5a6261270be249cc8bb0533c0da9f1b5.png

    可以看到,现在该问题就已经变成了在该树(图)中,2结点到7结点的最短路,使用BFS解决:

    1. import java.util.Deque;
    2. import java.util.LinkedList;
    3. import java.util.Scanner;
    4. public class Main {
    5. static class Node{
    6. int val;
    7. int step;
    8. public Node(int val, int step) {
    9. this.val=val;
    10. this.step=step;
    11. }
    12. }
    13. public static void main(String[] args) {
    14. Scanner sc = new Scanner(System.in);
    15. int m = sc.nextInt(), n = sc.nextInt();
    16. //这个判断是必须的,但是可以只判断m==n,因为后续的代码中如果m==n将出现死循环,m>n虽然不会出现死循环,但是我们在这里就能直接得到答案,既然如此就顺便一起处理了
    17. if(m>=n){
    18. System.out.println(m-n);
    19. return;
    20. }
    21. boolean[] vis = new boolean[100005];
    22. Deque q = new LinkedList<>();
    23. q.add(new Node(m,0));
    24. vis[m] = true;
    25. while( !q.isEmpty() ) {
    26. Node tmp = q.poll();
    27. int x;
    28. for(int i=0; i<3; ++i) {
    29. if(i==0) x = tmp.val+1;
    30. else if(i==1) x = tmp.val-1;
    31. else x = tmp.val*2;
    32. if(x>=0 && x<=100000 && !vis[x]) {
    33. if(x==n) {
    34. System.out.println(tmp.step+1);
    35. return;
    36. }
    37. q.add(new Node(x,tmp.step+1));
    38. vis[x]=true;
    39. }
    40. }
    41. }
    42. }
    43. }

    例2:八数码

    八数码是BFS的典型应用之一,其也可以转化为求图上两节点间的最短间距:每个八数码都可以将空格块与它上下左右相邻块交换,这样一个八数码可以扩展出四个新的八数码,这个扩展过程正是BFS的过程。以‘’283104765‘’为例,其BFS过程如下图所示:

    c156dd6fb5574c5ca5371e8d2c1f2832.png

    同样,问题转化为无权图上求最短路,示例代码如下所示:

    1. public void main(String[] args) {
    2. Scanner sc = new Scanner(System.in);
    3. String s = sc.nextLine();
    4. if(s.equals("123804765") {
    5. System.out.println(0);
    6. return;
    7. }
    8. int[] dx = {-1,1,0 ,0};
    9. int[] dy = {0 ,0,-1,1};
    10. ArrayDeque q = new ArrayDeque<>();
    11. q.add(s);
    12. Map ans = HashMap<>();
    13. while(!q.isEmpty()) {
    14. String tmp = q.poll();
    15. int index = tmp.indexOf('0');
    16. for(int i = 0; i < 4; ++i) {
    17. int x = index / 3 + dx[i], y = index % 3 + dy[i];
    18. if((x >= 0 && x < 3) && (y >= 0 && y < 3)) {
    19. int target = x * 3 + y;
    20. String str = swap(tmp, index, target);
    21. if(!ans.containsKey(str)) {
    22. ans.put(str, ans.getOrDefault(str, 0) + 1);
    23. if(str.equals("123804765") {
    24. System.out.println(ans.get(str));
    25. return;
    26. }
    27. q.add(str);
    28. }
    29. }
    30. }
    31. }
    32. }
    33. private static String swap(String s, int i, int j) {
    34. char[] str = s.toCharArray();
    35. char tmp = str[i];
    36. str[i] = str[j];
    37. str[j] = tmp;
    38. return new String(str);
    39. }

    三、BFS与迷宫问题

    迷宫问题之所以能用广搜解答,是因为我们用来表示迷宫的矩阵,实际上也能看做一张图,要求走出迷宫的最小步数,就等于图中起始结点与目标结点的最短路。

    1.走迷宫

    一个n*m的矩阵,'.'代表可走,'#'代表不可走,现要从左上角走到右下角(只能走上下左右),最小步数是多少?

    在每个位置都能朝上下左右四个方向走,也就是这个结点能扩展出四个新的结点,这个过程就是BFS的过程(注意不要超出迷宫范围以及不要重复搜索),解决迷宫问题的模板如下:

    1. import java.util.LinkedList;
    2. import java.util.Scanner;
    3. public class Main{
    4. //记录坐标
    5. static class Point{
    6. int x;
    7. int y;
    8. int step;
    9. public Point(int x, int y, int step) {
    10. this.x=x;
    11. this.y=y;
    12. this.step=step;
    13. }
    14. }
    15. public static void main(String[] args) {
    16. Scanner sc = new Scanner(System.in);
    17. int n = sc.nextInt(), m = sc.nextInt();
    18. sc.nextLine(); //有一个回车
    19. char[][] map = new char[n][m];
    20. for(int i=0; i
    21. String s = sc.nextLine();
    22. for(int j=0; j
    23. map[i][j]=s.charAt(j);
    24. }
    25. }
    26. //上下左右
    27. int[] dx = {-1,1,0 ,0};
    28. int[] dy = {0,0 ,-1,1};
    29. LinkedList q = new LinkedList<>();
    30. map[0][0]='#';//起点
    31. Point p = new Point(0,0,1);
    32. q.add(p);
    33. while(!q.isEmpty()) {
    34. Point tmp = q.poll();
    35. //向上下左右四个方向遍历
    36. for(int i=0; i<4; ++i) {
    37. int x = tmp.x+dx[i], y = tmp.y+dy[i];
    38. //新坐标在迷宫内且是可走的
    39. if(x>=0 && x=0 && y'#') {
    40. Point point = new Point(x,y,tmp.step+1);
    41. //如果到达终点就输出
    42. if(x==n-1 && y==m-1) {
    43. System.out.println(point.step);
    44. return;
    45. }
    46. q.add(point);
    47. //走过的迷宫改为不可走,广搜不需要走回该位置
    48. map[x][y]='#';
    49. }
    50. }
    51. }
    52. System.out.println("impossible!");
    53. }
    54. }

    四、BFS与连通块问题

    连通块问题是迷宫问题的一个扩展,此时题目要求的不再是最少步数,而是询问迷宫内有多少区域是连通的(不同题目的连通条件可能不同)。

    2.细胞计数

    在每个不为障碍的位置都进行一次BFS,将所有能扩展出的结点标记,后序被标记的位置不再重复BFS,下面的代码是该类型问题的模板,请读者对照自行实现:

    1. import java.util.Deque;
    2. import java.util.LinkedList;
    3. import java.util.Scanner;
    4. public class Main{
    5. static class Point{
    6. int x;
    7. int y;
    8. public Point(int x, int y) {
    9. this.x=x;
    10. this.y=y;
    11. }
    12. }
    13. public static void main(String[] args) {
    14. Scanner sc = new Scanner(System.in);
    15. int m = sc.nextInt(), n = sc.nextInt();
    16. int[][] map = new int[m][n];
    17. for(int i=0; i
    18. String s = sc.next();
    19. for(int j=0; j
    20. map[i][j]=s.charAt(j)-'0';
    21. }
    22. }
    23. int[] dx={-1,1,0,0};
    24. int[] dy={0,0,-1,1};
    25. int ans = 0;
    26. for(int i=0; i
    27. for(int j=0; j
    28. if(map[i][j]==0) continue;
    29. ans++; //计算共有多少个连通块
    30. map[i][j]=0;
    31. Deque q = new LinkedList<>();
    32. q.add(new Point(i,j));
    33. while(!q.isEmpty()) {
    34. Point tmp = q.poll();
    35. for(int k=0; k<4; ++k) {
    36. int x=tmp.x+dx[k], y=tmp.y+dy[k];
    37. if((x>=0 && x=0 && y0) {
    38. q.add(new Point(x,y));
    39. map[x][y]=0;
    40. }
    41. }
    42. }
    43. }
    44. }
    45. System.out.println(ans);
    46. }
    47. }

    3.城堡问题

    本题仍然是连通问题,但是其连通方式不同于上面的习题,本题中以每个位置数字的二进制来表示是否连通,每个数字的范围限定为4位二进制(即0~15),正好代表四个方向,从高位到低位依次为南、东、北、西(下、右、上、左),而该位为0表示连通,为1则表示不连通。那么本题就需要判断每个数字其二进制每一位的值,这个问题可以通过位运算很快地解决,如我想得到数字X的二进制第3位(从低位数)上的值,通过下面的位运算即可得到:(X>>3)&1。由于本题要计算二进制上每一位的值,所以需要用到原数字,那么就需要一个新的数组来判断每一个数字是否被访问过,完整示例代码如下所示:

    1. import java.util.Deque;
    2. import java.util.LinkedList;
    3. import java.util.Scanner;
    4. public class Main{
    5. static class Point{
    6. int x;
    7. int y;
    8. public Point(int x, int y) {
    9. this.x=x;
    10. this.y=y;
    11. }
    12. }
    13. public static void main(String[] args) {
    14. Scanner sc = new Scanner(System.in);
    15. int m = sc.nextInt(), n = sc.nextInt();
    16. int[][] map = new int[m][n];
    17. // 一个新数组判断每个数字是否被访问过
    18. boolean[][] vis = new boolean[m][n];
    19. for(int i=0; i
    20. for(int j=0; j
    21. map[i][j]=sc.nextInt();
    22. vis[i][j]=false;
    23. }
    24. }
    25. // 左,上,右,下,这个顺序不能改动,因为后面右移运算是顺序是低位到高位
    26. int[] dx={0,-1,0,1};
    27. int[] dy={-1,0,1,0};
    28. int ans = 0, max = 0;
    29. for(int i=0; i
    30. for(int j=0; j
    31. if(vis[i][j]) continue;
    32. ans++; //计算共有多少个连通块
    33. vis[i][j]=true;
    34. Deque q = new LinkedList<>();
    35. q.add(new Point(i,j));
    36. int cnt = 0;
    37. while(!q.isEmpty()) {
    38. Point tmp = q.poll();
    39. cnt++; //计算每个连通块有多少个数字
    40. for(int k=0; k<4; ++k) {
    41. int x=tmp.x+dx[k], y=tmp.y+dy[k];
    42. if((x>=0 && x=0 && y>k)&1)==0) {
    43. q.add(new Point(x,y));
    44. vis[x][y]=true;
    45. }
    46. }
    47. }
    48. max = Math.max(max, cnt);
    49. }
    50. }
    51. System.out.println(ans);
    52. System.out.println(max);
    53. }
    54. }
  • 相关阅读:
    小白必看!画出自己第一个界面,PyQt5安装以及使用
    用队列实现栈-力扣
    C++基础知识(十四)--- vector容器
    Mybatis-Plus复习
    KubeSphere 3.3.0 离线安装教程
    CSI(Container Storage Interface)
    Ubuntu18.04 ros 安装ZED SDK---基于x86架构
    2023高教社杯 国赛数学建模C题思路 - 蔬菜类商品的自动定价与补货决策
    JSP out.print()和out.write()方法的不同之处
    git安装部署与gitee的配置
  • 原文地址:https://blog.csdn.net/yuhaomogui/article/details/125424851