对简单路径问题的分析
问题:假设有这么一个二维数组,1代表墙不可以通过,0代表通路可以通过,试问给出起点(1, 1)和终点(7,7)可不可以给出一条通路、最短通路、以及所有通路(假设每个点都可以找八个方向)。(这些问题有人可能叫迷宫问题等,但是我理解的迷宫你只能知道起点的坐标,不能知道终点,所以对于知道起点和终点的问题我更愿意叫做路径搜索问题)
1 1 1 1 1 1 1 1 1
1 0 0 1 1 0 1 1 1
1 1 0 0 0 0 0 0 1
1 0 1 0 0 1 1 1 1
1 0 1 1 1 0 0 1 1
1 1 0 0 1 0 0 0 1
1 0 1 1 0 0 0 1 1
1 1 1 1 1 1 1 0 1
1 1 1 1 1 1 1 1 1
分析:对于这个问题,我们可以联想到有最基本的广度优先策略和深度优先策略。
1.深度优先就是一条道走到底,走不去了就"回溯"(回溯的实现就是利用递归的栈来实现,栈顶函数弹出栈,就可以达到回溯的目的)知道找到终点为止。
这个算法就是递归,用到了系统的函数栈。
2.广度优先优先,就是从起点开始,假设你可以分身你可以从该点开始同时对这八个方向进行探索,然后对每个可以走的点继续利用"分身"来对每个点
来进行上面所述的对八个方向同时进行探索知道找到终点为止。这个算法主要用到了队列,这时最先找到的就是最短的路径。
对上述问题的解决
我们已经知道最短路径的探索方法,那么一条普通的路径就可以解决。那么对于探索上述迷宫的所有路径,我们只需要利用一下深度优先的"回溯"原理就好。我们对于找到一条通路时,不要直接输出路径后就return程序,而应该和遇到障碍时一样,来进行回溯。直到回溯完起点的八个为止为止。这个有点像求八皇后问题的回溯法一样。
代码实现
广度优先求最短路径
public class Point { //首先定义了一个位置类, (x, y)代表坐标
public final int x;
public final int y;
public Point(int x, int y){ //构造器,用于初始化位置
this.x = x;
this.y = y;
}
}
import java.util.Date;
import java.util.LinkedList;
import java.util.Scanner;
public class BfsShort {
private final int[][] maze; //定于一个二维数组来表示迷宫
private final Point s; //定义了起始点
private final Point e; //定义了终点
private Point[][] patTo; //用于保存路径 //定义了八个方向
private final int[][] directory = {
{0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}};
private LinkedList<Point> stack = new LinkedList<>(); //没有用双向链表来模拟队列(队列的名字写成了栈的英语单词,忘记改了,手动狗头)
private BfsShort(int[][] maze, Point s, Point e){ //构造器读入地图,起点和 终点
this.maze = maze;
this.s = s;
this.e = e;
patTo = new Point[maze.length][maze[0].length];
bfs(); //调用了广度优先函数
print(); //打印出最段路径
}
private void bfs(){
stack.addLast(this.s); //首先将起点加入队列
this.maze[s.x][s.y] = 2; //将走过的点标记为2,表示走 过了
while (!stack.isEmpty()){ //当队列为空时程序结束,此时没有找到路径,直接将用于patTo保存路径的数组赋值为null,打印出一行话"该路径没有路径"
Point p = stack.removeFirst();
int x = p.x;
int y = p.y;
for (int i = 0; i < 8; i++) { //开始分身,对八个方将进行探索
int t = x + directory[i][0]; //下一个位置
int v = y + directory[i][1];
if (judge(t , v)){ //判断下一个位置是不是可以走,如果可以走judge函数返回ture;
patTo[t][v] = p; //将当前位置指向父位置,这样便于最后打印出路径
this.maze[t][v] = 2; //将走过的路标记为2
stack.addLast(new Point(t, v)); //将该位置入队列
if (t == this.e.x && v == this.e.y) //当找到终点时,直接找到路径程序结束,如果没有找到终点继续while循环
return;
}
}
}
patTo = null;
System.out.println("该迷宫没有路径");
}
private void print(){
if (patTo == null) //如果没有找到路径,直接结束程序,不打印
return;
Point temp = this.e; //如果有路径,则以终点为起始点开始,来继续对路径的打印
while (temp.x != this.s.x || temp.y != this.s.y){ //直到找到起点循环结束
System.out.print("[" + temp.x + " " + temp.y + "]");
temp = patTo[temp.x][temp.y]; //打印了temp位置后,然后找到它的父位置
}
System.out.println("[" + this.s.x + " " + this.s.y + "]"); //最后打印出起点位置
}
private boolean judge(int x, int y){
if (x < 0 || x >= maze.length || y < 0 || y >= maze[0].length) //如果越界,直接返回false
return false;
if (maze[x][y] == 1 || maze[x][y] == 2) //如果是障碍或者已经走过了直接返回false
return false;
else return true; //如果上述条件都不满足,那么就可以走,返回ture
}
public BfsShort(int[][] maze, int x, int y, int g, int h){ //对于用户只提供该方法其他方法全部为私有方法。
this(maze, new Point(x, y), new Point(g, h));
}
}
上述代码只对用于提供接口 public BfsShort(int[][] maze, int x, int y, int g, int h),打印出该迷宫的最短路径
测试上述迷宫:
public static void main(String[] args) {
int[][] maze = new int[9][9];
Scanner in = new Scanner(System.in);
for (int i = 0; i < maze.length; i++)
for (int j = 0; j < maze[0].length; j++)
maze[i][j] = in.nextInt();
BfsShort f = new BfsShort(maze, 1, 1, 7, 7);
}
结果如下
深度优先打印出所有路径
这个代码也会利用到上述的Point类,和上述代码相同含义的遍历已经判断我就不在解释第二遍了
具体的代码实现
import java.util.LinkedList;
import java.util.Scanner;
public class DfsAll {
//深度优先打印出迷宫所有可行的路径
private int count = 0;
private int[][] maze;
private final Point s;
private final Point e;
private LinkedList<Point> patTo = new LinkedList<>(); //利用链表,来储存当前探索的路径
private final int[][] directory = {
{0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}};
private DfsAll(int[][] maze, Point s, Point e){
this.maze = maze;
this.s = s;
this.e = e;
dfs(this.s); //调用深度优先打印打印出所有的路径
}
private boolean judge(int x, int y){
if (x < 0 || x >= maze.length || y < 0 || y >= maze[0].length)
return false;
if (maze[x][y] == 1 || maze[x][y] == 2)
return false;
else return true;
}
private void dfs(Point s){
this.patTo.addLast(s); //将s加入路径链表中
if (maze[this.e.x][this.e.y] == 2) //如果该点s为终点
printPat(); //打印出一条路径
else { //如果不是的话,找到可以深入的点深入递归下去
int x = s.x;
int y = s.y;
for (int i = 0; i < 8; i++)
if (judge(x + directory[i][0], y + directory[i][1])){ //如果该点可以走的话
this.maze[x + directory[i][0]][y + directory[i][1]] = 2; //标记该点已经走过
dfs(new Point(x + directory[i][0], y + directory[i][1])); //以该点为s继续递归
}
}
this.patTo.removeLast();
this.maze[s.x][s.y] = 0;
//以上两行是重点: 当走不去了,或者该点已经是终点了,将该点从路径数组中除出,回到上一步的for循环中,对其他剩下的方向进行探索,不过没有路径继续向上回溯,如果有继续向下走,该程序直达把其他的八个方向依次探索完为止,这里有点八皇后问题的味道,可以对比着看。
}
private void printPat(){ //打印出一条路径
if (this.patTo.isEmpty())
return;
System.out.print(++count + ":"); //count用于记录路径的数量
for (Point to : this.patTo)
System.out.print("[" + to.x + " " + to.y + "]");
System.out.println();
}
public DfsAll(int[][] maze, int x, int y, int g, int h){
this(maze, new Point(x, y), new Point(g, h));
}
}
测试如下:
public static void main(String[] args) {
int[][] maze = new int[9][9];
Scanner in = new Scanner(System.in);
for (int i = 0; i < maze.length; i++)
for (int j = 0; j < maze[0].length; j++)
maze[i]