本科学习数据结构时实践比较少,因此最近开始复习,看到网站http://learn.akae.cn/media/ch12s03.html,既可以打基础,又可以顺便学习Linux的知识。
第12章讲“堆栈”,第3节讲“深度优先算法”寻找路径迷宫。代码已经给出了,为了方便其他同学下载调试,我这里也附上:
原始代码:
#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
struct point {
int row;
int col;
} stack[512];
int top = 0;
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}
};
void push(struct point p)
{
stack[top++] = p;
}
struct point pop()
{
return stack[--top];
}
int is_empty()
{
return top == 0;
}
void print_maze(void)
{
int i, j;
for (i = 0; i < MAX_ROW; i++) {
for (j = 0; j < MAX_COL; j++)
printf("%d ", maze[i][j]);
putchar('\n');
}
printf("*********\n");
}
struct point predecessor[MAX_ROW][MAX_COL] = {
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}
};
void visit(int row, int col, struct point pre)
{
struct point visit_point = { row, col };
maze[row][col] = 2;
predecessor[row][col] = pre;
push(visit_point);
}
int main()
{
struct point p = { 0, 0 };
maze[p.row][p.col] = 2;
push(p);
while (!is_empty()) {
p = pop();
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
break;
if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
visit(p.row, p.col + 1, p);
if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
visit(p.row + 1, p.col, p);
if (p.col > 0 && maze[p.row][p.col - 1] == 0)
visit(p.row, p.col - 1, p);
if (p.row > 0 && maze[p.row - 1][p.col] == 0)
visit(p.row - 1, p.col, p);
print_maze();
}
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
printf("(%d, %d)\n", p.row, p.col);
while (predecessor[p.row][p.col].row != -1) {
p = predecessor[p.row][p.col];
printf("(%d, %d)\n", p.row, p.col);
}
} else
printf("No path!\n");
return 0;
}
下面有三道习题。
1. 正向打印路径
文中已经分析“正向打印路径”原理上的不可行,因此我想到的方法是:
A:把输出到路径压到一个新的堆栈中,然后pop出来——这很好理解。
B:取巧的做法——把迷宫出口(4,4)当作起点,入口(0,0)当作终点,找到的路径(4,4)->(0,0)逆序输出即为(0,0)->(4,4),倘若没有路径,那么只会输出“No pathing!”
B方法代码更改的地方很少:
#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
struct point {
int row;
int col;
} stack[512];
int top = 0;
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}
};
void push(struct point p)
{
stack[top++] = p;
}
struct point pop()
{
return stack[--top];
}
int is_empty()
{
return top == 0;
}
void print_maze(void)
{
int i, j;
for (i = 0; i < MAX_ROW; i++) {
for (j = 0; j < MAX_COL; j++)
printf("%d ", maze[i][j]);
putchar('\n');
}
printf("*********\n");
}
struct point predecessor[MAX_ROW][MAX_COL] = {
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}
};
void visit(int row, int col, struct point pre)
{
struct point visit_point = { row, col };
maze[row][col] = 2;
predecessor[row][col] = pre;
push(visit_point);
}
int main()
{
struct point p = { MAX_ROW-1, MAX_COL-1 }; // 起点为迷宫终点
maze[p.row][p.col] = 2;
push(p);
while (!is_empty()) {
p = pop();
if (p.row == 0 && p.col == 0) // 终止条件为达到起点
break;
if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
visit(p.row, p.col + 1, p);
if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
visit(p.row + 1, p.col, p);
if (p.col > 0 && maze[p.row][p.col - 1] == 0)
visit(p.row, p.col - 1, p);
if (p.row > 0 && maze[p.row - 1][p.col] == 0)
visit(p.row - 1, p.col, p);
print_maze();
}
if (p.row == 0 && p.col == 0) {
printf("(%d, %d)\n", p.row, p.col);
while (predecessor[p.row][p.col].row != -1) { // 最后只剩下起点以及未被访问到点到predecessor值为(-1,-1)
p = predecessor[p.row][p.col];
printf("(%d, %d)\n", p.row, p.col);
}
} else
printf("No path!\n");
return 0;
}
读者会发现,这种方法最终的结果maze矩阵中出现了0,原因是:本算法是深度优先算法,假如第一条路径就能够到达终点,那么算法就不会再计算了。
由此读者可以想到延伸的问题:求迷宫有几条路径可以达到终点?进而延伸:每条路径的长度是多少?这两个问题等到我以后再研究时会再练习,本文就先略去了。
2. 缩小空间
原来predecessor存储的是point的二维数组,我首先想到的是将其改为二维整形数据——代表指向前一个位置的“方向”。visit函数需要传入这个方向值;最后逆序输出时也需要根据方向值找到下一个位置点。首先,四个方向宏定义。
#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
#define RIGHT 1
#define LEFT 2
#define UP 3
#define DOWN 4
struct point {
int row;
int col;
} stack[512];
int top = 0;
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}
};
void push(struct point p)
{
stack[top++] = p;
}
struct point pop()
{
return stack[--top];
}
int is_empty()
{
return top == 0;
}
void print_maze(void)
{
int i, j;
for (i = 0; i < MAX_ROW; i++) {
for (j = 0; j < MAX_COL; j++)
printf("%d ", maze[i][j]);
putchar('\n');
}
printf("*********\n");
}
int predecessor[MAX_ROW][MAX_COL] = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
void visit(int row, int col, struct point pre, int Dir)
{
struct point visit_point = { row, col };
maze[row][col] = 2;
predecessor[row][col] = Dir;
push(visit_point);
}
int main()
{
struct point p = { 0, 0 };
maze[p.row][p.col] = 2;
push(p);
while (!is_empty()) {
p = pop();
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
break;
if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
visit(p.row, p.col + 1, p, LEFT);
if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
visit(p.row + 1, p.col, p, UP);
if (p.col > 0 && maze[p.row][p.col - 1] == 0)
visit(p.row, p.col - 1, p, RIGHT);
if (p.row > 0 && maze[p.row - 1][p.col] == 0)
visit(p.row - 1, p.col, p, DOWN);
print_maze();
}
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
printf("(%d, %d)\n", p.row, p.col);
while (predecessor[p.row][p.col] != 0) {
switch (predecessor[p.row][p.col]) {
case UP:
p.row -= 1;
break;
case DOWN:
p.row += 1;
break;
case LEFT:
p.col -= 1;
break;
case RIGHT:
p.col += 1;
break;
}
// p = predecessor[p.row][p.col];
printf("(%d, %d)\n", p.row, p.col);
}
} else
printf("No path!\n");
return 0;
}
起先设置predecessor的初始值都是0,最终找到的路径上点到predecessor的值则为LEFT/UP/RIGHT/DOWN,因此沿着已知的方向依次判断下个坐标点到位置,直到preecessor值=0的起点。
更进一步,我想,predecessor和maze都是2维整形数组,为什么不能把两者合并呢?于是修改如下:(为了防止数据混淆,修改原来到宏定义)
#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
#define RIGHT 3
#define LEFT 4
#define UP 5
#define DOWN 6
struct point {
int row;
int col;
} stack[512];
int top = 0;
int maze[5][5] = {
{0, 0, 0, 0, 0},
{1, 0, 1, 1, 0},
{0, 0, 1, 1, 1},
{0, 1, 0, 0, 0},
{0, 0, 0, 1, 0}
};
void push(struct point p)
{
stack[top++] = p;
}
struct point pop()
{
return stack[--top];
}
int is_empty()
{
return top == 0;
}
void print_maze(void)
{
int i, j;
for (i = 0; i < MAX_ROW; i++) {
for (j = 0; j < MAX_COL; j++)
printf("%d ", maze[i][j]);
putchar('\n');
}
printf("*********\n");
}
/*
int predecessor[MAX_ROW][MAX_COL] = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
*/
void visit(int row, int col, struct point pre, int Dir)
{
struct point visit_point = { row, col };
maze[row][col] = Dir;
// predecessor[row][col] = Dir;
push(visit_point);
}
int main()
{
struct point p = { 0, 0 };
maze[p.row][p.col] = 2; // 起始点的方向设置为2,代表无方向。
push(p);
while (!is_empty()) {
p = pop();
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
break;
if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
visit(p.row, p.col + 1, p, LEFT);
if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
visit(p.row + 1, p.col, p, UP);
if (p.col > 0 && maze[p.row][p.col - 1] == 0)
visit(p.row, p.col - 1, p, RIGHT);
if (p.row > 0 && maze[p.row - 1][p.col] == 0)
visit(p.row - 1, p.col, p, DOWN);
print_maze();
} // 倘若有路径,那么最后pop出的p即(4,4)
if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
printf("(%d, %d)\n", p.row, p.col);
// 起点的方向是2,其他皆为1/3/4/5/6
// 0代表尚未访问到点:当第一条路径就打到终点时,其它路径也就没有再访问了
while (maze[p.row][p.col] != 2) {
switch (maze[p.row][p.col]) {
case UP:
p.row -= 1;
break;
case DOWN:
p.row += 1;
break;
case LEFT:
p.col -= 1;
break;
case RIGHT:
p.col += 1;
break;
}
// p = predecessor[p.row][p.col];
printf("(%d, %d)\n", p.row, p.col);
}
} else // 倘若最后pop出的非(4,4),那么就没有路径
printf("No path!\n");
return 0;
}
3. 堆栈变递归
待续。。。