本科學習數據結構時實踐比較少,因此最近開始複習,看到網站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. 堆棧變遞歸
待續。。。