递归替代堆栈实现走迷宫
根据堆栈实现迷宫的前向查找分容易,但是错误回溯是个问题
第一次思考
最开始我想到很简单,假设死路终点回退,
怎么判断死路终点呢?只有当前这点为0,周围要么到了边界,要么非0,那么就置当前点为死路顶点。
将死路顶点标记-1, 再以死路顶点为输入根据路线递归倒退即可。
倒退规则:
- 当前点为-1
- 当前点周围全为非0
- 与当前点相邻的标记为2的块即为倒退点,将其标记为-1,下一轮迭代。
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
0 | 0 | 0 | 0 |
以左下为起点,右上为终点,假设查找顺序为 → \rightarrow → ↑ \uparrow ↑ ← \leftarrow ← ↓ \downarrow ↓ , 那么路径图就是
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | 2 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | 2 | 2 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | 2 | -1 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | 2 | -1 | -1 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | -1 | -1 | -1 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
2 | -1 | -1 | -1 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
-1 | -1 | -1 | -1 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
2 | 1 | 1 | 1 |
-1 | -1 | -1 | -1 |
问题就出在回溯的最后一步,他会把死区入口点也当作死区,导致这个问题的源头在于我手动将死区终点赋值为1,打断了递归,相当于在死区内又用另外一个递归
这个事情最大的教训就是递归就得一条顺下来,越多的手动重置就会产生越多的后续调整
第二次思考
根据上一个教训的反思,修改一下一路顺下来
递归输入点 A
if A 有出路点 B
YES
A 置 2
递归输入点 B
No
A 置 -1
递归输入 A 的上一点 C
但是,死区如何输入 A 的上一点 C ?
如果采用一个数据存储上一点,由于递归的特性,压根没有节省内存的意义
但是好像可以不需要存储,只需要前进和后退存在某种关系即可
地图
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
0 | 1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
以左下为起点,右上为终点,假设查找顺序为 → \rightarrow → ↑ \uparrow ↑ ← \leftarrow ← ↓ \downarrow ↓ , 那么前进路径图就是
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
0 | 1 | ↓ \downarrow ↓ | ← \leftarrow ← | ← \leftarrow ← |
0 | 1 | → \rightarrow → | -1 | ↑ \uparrow ↑ |
→ \rightarrow → | → \rightarrow → | → \rightarrow → | → \rightarrow → | ↑ \uparrow ↑ |
而必要的回溯路径图就是
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
0 | 1 | → \rightarrow → | → \rightarrow → | ↓ \downarrow ↓ |
0 | 1 | ↑ \uparrow ↑ | ← \leftarrow ← | ↓ \downarrow ↓ |
2 | ← \leftarrow ← | ← \leftarrow ← | ← \leftarrow ← | ← \leftarrow ← |
设置这个迷宫图是因为死区终点情况最复制,可以从4个方向返回,但是为了包装回溯的正确性,必须是 左向优先级最高,同时得到的第二个信息,前进是顺时针,回溯就是逆时针。
根据这两个信息和原始的前进顺序对比,有个猜想
- 回溯的最高优先级必定与前进的最高优先级相反
- 如果前进时顺时针的,那么回溯必须时逆时针的
根据这两条规则,得到的回溯顺序为
←
\leftarrow
←
↑
\uparrow
↑
→
\rightarrow
→
↓
\downarrow
↓
回溯的顺序时符合这个要求的,但是为了检测这个猜想的正确性,再设计一个地图验证一下
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 0 |
0 | 0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
以左下为起点,右上为终点,假设前进查找顺序为 → \rightarrow → ↑ \uparrow ↑ ← \leftarrow ← ↓ \downarrow ↓ , 对于的回溯顺序 ← \leftarrow ← ↑ \uparrow ↑ → \rightarrow → ↓ \downarrow ↓ ,那么前进路径图就是
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
→ \rightarrow → | → \rightarrow → | → \rightarrow → | → \rightarrow → | ↓ \downarrow ↓ |
↑ \uparrow ↑ | 1 | 1 | 1 | ↓ \downarrow ↓ |
↑ \uparrow ↑ | ← \leftarrow ← | 1 | ↓ \downarrow ↓ | ← \leftarrow ← |
0 | ↑ \uparrow ↑ | 1 | → \rightarrow → | -1 |
→ \rightarrow → | ↑ \uparrow ↑ | 1 | 0 | 1 |
这个地图比较奇怪的一点是走到了死区终点,居然在整个大死区内还有一个未访问点,安装前进与回溯的要求,回溯部分区域后会再次访问未访问点
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
2 | 2 | 2 | 2 | 2 |
2 | 1 | 1 | 1 | 2 |
2 | 2 | 1 | 2 | 2 |
0 | 2 | 1 | ↓ \downarrow ↓ (2) | ← \leftarrow ← |
2 | 2 | 1 | -1 | 1 |
注意到那个点可以前进访问0值点,所以被置2,后续回溯过程
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
2 | 2 | 2 | 2 | 2 |
2 | 1 | 1 | 1 | 2 |
2 | 2 | 1 | 2 | 2 |
0 | 2 | 1 | ↓ \downarrow ↓ (2) | ← \leftarrow ← |
2 | 2 | 1 | -1 | 1 |
后续的回溯
0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 | 1 |
2 | ← \leftarrow ← | 2 ← \leftarrow ← | 2 ← \leftarrow ← | ← \leftarrow ← |
2 | 1 | 1 | 1 | ↑ \uparrow ↑ |
2 | 2 | 1 | → \rightarrow → | ↑ \uparrow ↑ |
0 | 2 | 1 | ↑ \uparrow ↑ | -1 |
2 | 2 | 1 | ↑ \uparrow ↑ | 1 |
明显是可以的
程序设计
使用递归的话就取消了堆栈,没必要定义堆栈了出栈入栈了
/*
* 深度优先算法解决迷宫问题
* 直接使用堆栈存储路线即可
*/
#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
/*
* 迷宫图, 1表示障碍, 0表示路径, 只允许上下左右移动
*/
int maze[MAX_ROW][MAX_COL] = {
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, //
};
struct point {
int row;
int col;
};
void print_maze(void) {
for (int i = 0; i < MAX_ROW; i++) {
for (int j = 0; j < MAX_COL; j++)
printf("%d\t", maze[i][j]);
printf("\n");
}
printf("**********\n");
}
void recurrent_search(struct point p) {
struct point p_next;
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
maze[p.row][p.col] = 2;
print_maze();
return;
} else {
/* go ahead */
if (maze[p.row][p.col] != 1 &&
((p.row + 1 <= MAX_ROW - 1 && maze[p.row + 1][p.col] == 0) ||
(p.row - 1 >= 0 && maze[p.row - 1][p.col] == 0) ||
(p.col + 1 <= MAX_COL - 1 && maze[p.row][p.col + 1] == 0) ||
(p.col - 1 >= 0 && maze[p.row][p.col - 1] == 0))) {
/* mark the current point 1 */
maze[p.row][p.col] = 2;
print_maze();
if (p.row + 1 <= MAX_ROW - 1 && maze[p.row + 1][p.col] == 0) {
p_next.row = p.row + 1;
p_next.col = p.col;
recurrent_search(p_next);
} else if (p.col + 1 <= MAX_COL - 1 && maze[p.row][p.col + 1] == 0) {
p_next.row = p.row;
p_next.col = p.col + 1;
recurrent_search(p_next);
} else if (p.row - 1 >= 0 && maze[p.row - 1][p.col] == 0) {
p_next.row = p.row - 1;
p_next.col = p.col;
recurrent_search(p_next);
} else if (p.col - 1 >= 0 && maze[p.row][p.col - 1] == 0) {
p_next.row = p.row;
p_next.col = p.col - 1;
recurrent_search(p_next);
}
} else if (maze[p.row][p.col] != 1 &&
(p.row + 1 > MAX_ROW - 1 || maze[p.row + 1][p.col] != 0) &&
(p.row - 1 < 0 || maze[p.row - 1][p.col] != 0) &&
(p.col + 1 > MAX_COL - 1 || maze[p.row][p.col + 1] != 0) &&
(p.col - 1 < 0 || maze[p.row][p.col - 1] != 0)) {
/* go back */
maze[p.row][p.col] = -1;
print_maze();
if (p.row - 1 >= 0 && maze[p.row - 1][p.col] == 2) {
p_next.row = p.row - 1;
p_next.col = p.col;
recurrent_search(p_next);
} else if (p.col + 1 <= MAX_COL - 1 && maze[p.row][p.col + 1] == 2) {
p_next.row = p.row;
p_next.col = p.col + 1;
recurrent_search(p_next);
} else if (p.row + 1 <= MAX_ROW - 1 && maze[p.row + 1][p.col] == 2) {
p_next.row = p.row + 1;
p_next.col = p.col;
recurrent_search(p_next);
} else if (p.col - 1 >= 0 && maze[p.row][p.col - 1] == 2) {
p_next.row = p.row;
p_next.col = p.col - 1;
recurrent_search(p_next);
}
}
}
}
int main(int argc, char *argv[]) {
struct point p = {0, 0};
recurrent_search(p);
return 0;
}
但是使用递归的话就无法保留路径了,除非说从终点再次逆序递归打印到起点,很麻烦着实很麻烦
第二个问题就是写这几个函数的时候很多重复的工作,有没有那种方式能减少这种工作量?OOP吗