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

与深度优先搜索类似,我们还是以图的搜索引入广度优先搜索的定义。如下是一张无向图,现对其进行广度优先遍历:

广度优先搜索BFS:图与迷宫_第1张图片

与深度优先搜索不同,广搜会优先搜索该结点所有可能的分支,而深搜则是沿某一分支一直搜到底。该图一种可能的BFS结果是:ABFCIGEDH。其具体实现可以利用队列完成:

假设起点为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,所以广搜又可以叫做层次遍历。广搜可以用来求得图上任意两结点间的最小间距,其就等于它们分别所在的层数之差的绝对值,如B到H的最小间距为4-2=2。这是一个十分广泛的应用,诸多问题都能依靠其解决,如下面两道例题:

例1:抓住那头牛

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

广度优先搜索BFS:图与迷宫_第2张图片

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

import java.util.Deque;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static class Node{
		int val;
		int step;
		public Node(int val, int step) {
			this.val=val;
			this.step=step;
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int m = sc.nextInt(), n = sc.nextInt();
        //这个判断是必须的,但是可以只判断m==n,因为后续的代码中如果m==n将出现死循环,m>n虽然不会出现死循环,但是我们在这里就能直接得到答案,既然如此就顺便一起处理了
        if(m>=n){
            System.out.println(m-n);
            return;
        }
		boolean[] vis = new boolean[100005];
		Deque q = new LinkedList<>();
		q.add(new Node(m,0));
		vis[m] = true;
		while( !q.isEmpty() ) {
			Node tmp = q.poll();
			int x;
			for(int i=0; i<3; ++i) {
				if(i==0) x = tmp.val+1;
				else if(i==1) x = tmp.val-1;
				else x = tmp.val*2;
				if(x>=0 && x<=100000 && !vis[x]) {
					if(x==n) {
						System.out.println(tmp.step+1);
						return;
					}
					q.add(new Node(x,tmp.step+1));
					vis[x]=true;
				}
			}
		}
	}
}

例2:八数码

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

广度优先搜索BFS:图与迷宫_第3张图片

示例代码如下所示:

public void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    String s = sc.nextLine();
    if(s.equals("123804765") {
        System.out.println(0);
        return;
    }
    int[] dx = {-1,1,0 ,0};
    int[] dy = {0 ,0,-1,1};
    ArrayDeque q = new ArrayDeque<>();
    q.add(s);
    Map ans = HashMap<>();
    while(!q.isEmpty()) {
        String tmp = q.poll();
        int index = tmp.indexOf('0');
        for(int i = 0; i < 4; ++i) {
            int x = index / 3 + dx[i], y = index % 3 + dy[i];
            if((x >= 0 && x < 3) && (y >= 0 && y < 3)) {
                int target = x * 3 + y;
                String str = swap(tmp, index, target);
                if(!ans.containsKey(str)) {
                    ans.put(str, ans.getOrDefault(str, 0) + 1);
                    if(str.equals("123804765") {
                        System.out.println(ans.get(str));
                        return;
                    }
                    q.add(str);
                }
            }
        }
    }
}
private static String swap(String s, int i, int j) {
    char[] str = s.toCharArray();
    char tmp = str[i];
    str[i] = str[j];
    str[j] = tmp;
    return new String(str);
}

而在这个应用上稍作扩展就得到了广度优先搜索的另一个典型应用——解决迷宫问题,如:

1.走迷宫

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

迷宫问题之所以能用广搜解答,是因为我们用来表示迷宫的矩阵,实际上就能看做一张图,要求走出迷宫的最小步数,实际就等于图中起始结点与目标结点的最小间距。下面的示例代码是解决迷宫问题的一个模板,刚学习BFS的读者请务必跟着代码自己实现一遍:

import java.util.LinkedList;
import java.util.Scanner;

public class Main{
    //记录坐标
	static class Point{
		int x;
		int y;
		int step;
		public Point(int x, int y, int step) {
			this.x=x;
			this.y=y;
			this.step=step;
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt(), m = sc.nextInt();
		sc.nextLine(); //有一个回车
		char[][] map = new char[n][m];
		for(int i=0; i q = new LinkedList<>();
		map[0][0]='#';//起点
		Point p = new Point(0,0,1);
		q.add(p);
		while(!q.isEmpty()) {
			Point tmp = q.poll();
            //向上下左右四个方向遍历
			for(int i=0; i<4; ++i) {
				int x = tmp.x+dx[i], y = tmp.y+dy[i];
                //新坐标在迷宫内且是可走的
				if(x>=0 && x=0 && y

上面是经典的迷宫问题,下面的一系列问题是在此基础上的一些变形:

2.细胞计数

这是迷宫问题的一个经典变形,将走迷宫问题改成询问迷宫内有多少区域是连通的(不同题目对连通的定义不同),下面的代码是该类型问题的模板,请读者对照自行实现:

import java.util.Deque;
import java.util.LinkedList;
import java.util.Scanner;

public class Main{
	static class Point{
		int x;
		int y;
		public Point(int x, int y) {
			this.x=x;
			this.y=y;
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int m = sc.nextInt(), n = sc.nextInt();
		int[][] map = new int[m][n];
		for(int i=0; i q = new LinkedList<>();
        		q.add(new Point(i,j));
        		while(!q.isEmpty()) {
        			Point tmp = q.poll();
            		for(int k=0; k<4; ++k) {
            			int x=tmp.x+dx[k], y=tmp.y+dy[k];
            			if((x>=0 && x=0 && y

3.岛屿数量

该题实际上与上题相同,代码请读者自行参考上题实现。

 4.Lake Counting

这是一道英文题,但是题意不难理解, 还是属于连通类型,只不过它的连通方式更多,上面的两题都只有上下左右四个方向连通,而本题还允许左上左下、右上右下共八个方向连通,只要在模板代码里修改dx与dy数组,增加循环次数为8即可。

5.城堡问题

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

import java.util.Deque;
import java.util.LinkedList;
import java.util.Scanner;

public class Main{
	static class Point{
		int x;
		int y;
		public Point(int x, int y) {
			this.x=x;
			this.y=y;
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int m = sc.nextInt(), n = sc.nextInt();
		int[][] map = new int[m][n];
        //一个新数组判断每个数字是否被访问过
		boolean[][] vis = new boolean[m][n];
		for(int i=0; i q = new LinkedList<>();
        		q.add(new Point(i,j));
        		int cnt = 0;
        		while(!q.isEmpty()) {
        			Point tmp = q.poll();
        			cnt++;//计算每个连通块有多少个数字
            		for(int k=0; k<4; ++k) {
            			int x=tmp.x+dx[k], y=tmp.y+dy[k];
            			if((x>=0 && x=0 && y>k)&1)==0) {
            				q.add(new Point(x,y));
            				vis[x][y]=true;
            			}
            		}
        		}
        		max = Math.max(max, cnt);
        	}
        }
        System.out.println(ans);
        System.out.println(max);
	}
}

你可能感兴趣的:(搜索算法,算法,leetcode,宽度优先)