搜索算法学习:BFS
广度优先搜索(Breadth FirstSearch,BFS)
BFS以广度为第一关键词,当碰到岔道口时,总是先依次访问从该岔道口能直接到达的所有结点,然后再按这些结点被访问的顺序去依次访问它们能直接到达的所有结点,以此类推,直到所有结点都被访问为止。这就跟平静的水面中投入一颗小石子一样,水花总是以石子落水处为中心,并以同心圆的方式向外扩散至整个水面
以一个走迷宫问题为例:
BFS的实现
从上面的概念能看出,BFS可以用队列来实现,且总是按层次的顺序进行遍历,其模板如下:
void BFS(int s){
Queue<cNode> queue = new LinkedList<cNode>();
queue.offer(s);
while(queue.size()!=0){
出队队首元素top并访问
将top的下一层结点中未曾入队的结点全部入队,并设置为已入队
}
}
BFS的使用
一般有两类问题通常使用BFS来解决,一是染色问题,二是迷宫最短路径问题
①染色问题:
对于这种类型的问题,求解思想基本是:枚举每一个位置的元素,如果是0就跳过。如果是1则使用BFS查询与该位置相邻的4个位置(出界的当然就不查了),判断他们是否为1,如果是1,就去查询与该位置相邻的4个位置,直到所有的1被访问完毕。这样的话就能查询标记出所有和第一个1相邻的1,这些1就构成了一个块。 而为了防止已经被查询过的1再次被查询,一般可以设置一个boolean数组inq来记录每个位置是否被查询过
代码的实现如下:
import java.util.*;
public class BFS染色 {
static class cNode{
int x;
int y;
};
static int[][] a = new int[6][7];
static boolean[][] inq = new boolean[6][7];
static int[]X = new int[]{1,-1,0,0};
static int[]Y = new int[]{0,0,1,-1};
static boolean judge(int x,int y){
if(x<0||x>5||y<0||y>6) return false;
if(a[x][y]==0) return false;
if(inq[x][y]==true) return false;
return true;
};
static void BFS(int x,int y){
cNode n1 = new cNode();
n1.x = x;
n1.y = y;
Queue<cNode> queue = new LinkedList<cNode>();
queue.offer(n1);
inq[x][y]=true;
while(queue.size()!=0){
cNode n2 = queue.poll();
for(int i=0;i<4;i++){
int newX = n2.x+X[i];
int newY = n2.y+Y[i];
if(judge(newX, newY)){
cNode n3 = new cNode();
n3.x = newX;
n3.y = newY;
queue.offer(n3);
inq[newX][newY]=true;
}
}
};
};
public static void main(String[] args) {
int ans = 0;
Scanner scanner = new Scanner(System.in);
for(int i=0;i<6;i++){
for(int j=0;j<7;j++){
a[i][j] = scanner.nextInt();
}
};
for(int i=0;i<6;i++){
for(int j=0;j<7;j++){
if(a[i][j]==1&&inq[i][j]==false){
BFS(i,j);
ans++;
}
};
};
System.out.println(ans);
}
}
②迷宫最短路径问题:
由于求的是最少步数,而 BFS是通过层次的顺序来遍历的,因此可以从起点S(层次0)开始计数遍历的层数,那么在到达终点T时的层数就是需要求解的起点S到达终点T的最少步数
于是可以写出下面的代码:
package 练习;
import java.util.*;
import 练习.BFS染色.cNode;
public class BFS迷宫 {
static class cNode{
int x;
int y;
int step;
}
static int M; //M*N的迷宫
static int N;
static cNode S = new cNode(); //起点
static cNode T = new cNode(); //终点
static String[][] a = new String[1000][1000];
static boolean[][] inq = new boolean[1000][1000];
static int[] X = new int[]{1,-1,0,0};
static int[] Y = new int[]{0,0,1,-1};
static cNode[][] route = new cNode[1000][1000];//cNode类型的二维数组,在存路径时用得到
static boolean judge(int x,int y){ //判断该点是否需要处理
if(x<0||y<0||x>=M||y>=N) return false;
if(a[x][y].equals("*")) return false;
if(inq[x][y]==true) return false;
return true;
};
static void BFS(int x,int y){
cNode n1 = new cNode();
n1.x = x;
n1.y = y;
n1.step = 0;
Queue<cNode> queue = new LinkedList<cNode>();
queue.offer(n1);
inq[x][y]=true;
while(queue.size()!=0){
cNode n2 = queue.poll();
if(n2.x == T.x &&n2.y==T.y){
System.out.println(n2.step); //输出最短步数
break;
};
for(int i=0;i<4;i++){
int newX = n2.x+X[i];
int newY= n2.y+Y[i];
if(judge(newX, newY)){
cNode n3 = new cNode();
n3.x = newX;
n3.y = newY;
n3.step = n2.step+1;
queue.offer(n3);
inq[newX][newY]=true;
route[newX][newY] = new cNode();
route[newX][newY] = n2; //这个二维数组,每一个点对应的数组存放着上一个点
}
}
}
}
//输出走过的路径
//因为二维数组每一个元素中存放的值是到达该点的上一个点,因此可以用递归的方法把起点走到终点所有的点都输出出来
static void print(int x,int y){
if(x == S.x&&y == S.y){
System.out.println("("+x+","+y+")");
}else {
print(route[x][y].x, route[x][y].y);
System.out.println("("+x+","+y+")");
}
}
public static void main(String[] args) {
M = 5;
N = 5;
Scanner scanner = new Scanner(System.in);
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
a[i][j]=scanner.next();
};
};
S.x = 2;
S.y = 2;
T.x = 4;
T.y = 3;
BFS(S.x,S.y);
print(T.x, T.y);
}
}
注:如果只求最短的步数是多少,只需要BFS(S.x,S.y)就够了。如果还需要最短步数所经过的点,就需要添加一个print(T.x, T.y)方法