华为oj迷宫问题求解(深度优先搜索和广度优先搜索)
深度优先搜索:
自己花了好长时间学习了深度优先搜索算法,受益颇多,网上许多资料都看不太懂,最后自己按着那个思想一步一步实现了,分享一下,以华为 oj 上的迷宫问题为例来说一下:
问题描述:
定义一个二维数组 N*M (其中 2<=N<=10;2<=M<=10 ),如 5×5 数组下所示:
int maze[5][5]={ 0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0, };
它表示一个迷宫,其中的 1 表示墙壁, 0 表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。入口点为 [0,0], 既第一空格是可以走的路。
Input 一个 N × M 的二维数组,表示一个迷宫。数据保证有唯一解 , 不考虑有多解的情况,即迷宫只有一条通道。 Output 左上角到右下角的最短路径,格式如样例所示。
Sample Input 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0
Sample Output (0, 0) (1, 0) (2, 0) (2, 1) (2, 2) (2, 3) (2, 4) (3, 4) (4, 4)
输入描述:
输入两个整数,分别表示二位数组的行数,列数。再输入相应的数组,其中的 1 表示墙壁, 0 表示可以走的路。数据保证有唯一解 , 不考虑有多解的情况,即迷宫只有一条通道。
输出描述:
左上角到右下角的最短路径,格式如样例所示。
输入例子:
5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0
输出例子:
(0,0) (1,0) (2,0) (2,1) (2,2) (2,3) (2,4) (3,4) (4,4)
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String args[]){
Scanner sc=new Scanner(System.in);
while (sc.hasNext()) {
int m = sc.nextInt();
int n = sc.nextInt();
int[][] map = new int[m][n];// 定义一个Map矩阵,用来保存地图
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
map[i][j] = sc.nextInt();// 输入地图
}
}
int[][] dir = {{1, 0}, {0, 1}};// 定义两个方向横着走或者竖着走(题目中说只走这两个方向,当然也可以定义多个方向)
Stack<Node> stack = new Stack<Node>();// 定义一个栈,保存路径
int[][] visited = new int[m][n];// 标记是否被访问,这个要和Map大小对应
Node start = new Node(0, 0);// 定义起始位置
Node end = new Node(m - 1, n - 1);// 定义目的位置
visited[start.x][start.y] = 1;// 将起始点标记为访问
stack.push(start);// 将起始点加入队列
while (!stack.isEmpty()) {// 如果对了为空了还没有找到解,说明就没有通路,当然本题不存在无解,题目上说了一定存在一个通路。
boolean flag = false;// 标记是否找了一个方向
Node pek = stack.peek();// 获取栈顶元素,注意不需要出栈
if (pek.x == end.x && pek.y == end.y) {// 如果到达目的地则跳出循环
break;
} else {
for (int i = 0; i < 2; i++) {// 循环两个方向
Node nbr = new Node(pek.x + dir[i][0], pek.y + dir[i][1]);// 找到当前位置的邻居位置坐标并判断是否合法
if (nbr.x >= 0 && nbr.x < m && nbr.y >= 0 && nbr.y < n && map[nbr.x][nbr.y] == 0 && visited[nbr.x][nbr.y] == 0) {// 判断邻居节点是否合法
stack.push(nbr);// 合法将邻居位置加入栈
visited[nbr.x][nbr.y] = 1;// 并标记该节点已经访问
flag = true;// 找到了一个方向
break;// 找到了就停止循环,顺着这个方向一直搜索
}
}
if (flag) {// 找到了方向,就不用执行下面的出栈,沿着这个方向一直搜下去
continue;
}
stack.pop();// 如果两个方向都不能通过,则出栈。
}
}
Stack<Node> stkRev = new Stack<Node>();// 将路径反过来,因为栈中输出的路径是反的
while (!stack.isEmpty()) {
stkRev.push(stack.pop());
}
while (!stkRev.isEmpty()) {
System.out.println("(" + stkRev.peek().x + "," + stkRev.peek().y + ")");
stkRev.pop();
}
}
}
}
class Node{
int x;
int y;
Node(int x,int y){
this.x=x;
this.y=y;
}
}
一般谈起广搜,我的第一反应就是求最短路径,因为广搜是由内向外逐层扩散,最后肯定能找到一个最短的路径,其实用法也不难 ,今天说的广搜和之前的不同之处在于,之前求最短路径,最终只需给出一个数字表示最短的长度,而不需要输出具体的路径是什么,也就是怎么走,而今天的广搜是需要输出路径,要是谈起这个路径问题,我想大部分人可能觉得深搜比较好,的确,我也是这样觉得,但是我们要灵活运行多种方法去解决一个问题,下来说说具体的题目吧。
广搜的套路就是利用一个队列,每次将当前点的邻居节点加入队列,然后出队,在将当前点加入队列,一直讲所有路径搜索完毕,直到队列为空停止。同时还需要一个数组去保存该节点是否访问,做个标记。但是怎样输出路径呢,我采取的办法是每次我们需要保存一下当前节点的前驱节点,可以这样设计一个类保存当前坐标和前驱坐标:
class Node{
int x;
int y;
int prex;
int prey;
}
这样最终我们可以通过前驱节点来输出整个的路径。具体怎样做请往下看,就拿迷宫问题给的样例来说吧,每次将所有出队的节点保存在一个
arrayList
中,最终
arrayList
中保存了所有出队的节点,我们怎样从这些节点确定一条路径呢?
输入
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
arrayList 中会保存 13 个节点,分别是
( 0,0,0,0 )
( 1,0,0,0 )
( 2,0,1,0 )
( 3,0,2,0 )
( 2,1,2,0 )
( 4,0,3,0 )
( 2,2,2,1 )
( 4,1,4,0 )
( 2,3,2,2 )
( 4,2,4,1 )
( 2,4,2,3 )
( 3,4,2,4 )
( 4,4,3,4 )
import java.util.*;
public class BFS {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (sc.hasNext()){
int m=sc.nextInt();
int n=sc.nextInt();
int[][] map = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
map[i][j] = sc.nextInt();
}
}
int[][] dir = {{1, 0}, {0, 1}};
int[][] visited=new int[m][n];
Node start=new Node(0,0);
Node end=new Node(m-1,n-1);
Queue<Node> queue=new LinkedList<Node>();
ArrayList<Node> arrayList=new ArrayList<Node>();// 用来保存每一个出队列的节点
queue.offer(start);
while (!queue.isEmpty()){
Node local=queue.remove();
arrayList.add(local);
for (int i=0;i<2;i++){
Node nbr=new Node(local.x+dir[i][0],local.y+dir[i][1]);
if(nbr.x>=0&&nbr.x<m&&nbr.y>=0&&nbr.y<n&&map[nbr.x][nbr.y]==0&&visited[nbr.x][nbr.y]==0){
visited[nbr.x][nbr.y]=1;
queue.offer(nbr);
nbr.prex=local.x;// 保存前驱节点
nbr.prey=local.y;// 保存前驱节点
}
}
}
Stack<Integer> stack=new Stack<Integer>();
int px=arrayList.get(arrayList.size()-1).prex;// 获得目的节点的前驱节点
int py=arrayList.get(arrayList.size()-1).prey;
stack.push(arrayList.size()-1);// 将目的节点在arrayList中的位置记录下来,便于输出
while (true){
if(px==0&&py==0){// 找到起始节点就停止
break;
}
for(int i=0;i<arrayList.size();i++){// 循环找出每一个节点的前驱,找到就跳出当前循环
if(arrayList.get(i).x==px&&arrayList.get(i).y==py){
px=arrayList.get(i).prex;
py=arrayList.get(i).prey;
stack.push(i);// 保存节点在arrayList中的位置
break;
}
}
}
System.out.println("(0,0)");
while (!stack.isEmpty()){
System.out.println("("+arrayList.get(stack.peek()).x+","+arrayList.get(stack.peek()).y+")");
stack.pop();
}
}
}
}
class Node{
int x;
int y;
int prex;// 保存前驱节点位置
int prey;
Node(int x,int y){
this.x=x;
this.y=y;
}
}
查看14道真题和解析