實驗要求和內容
迷宮只有兩個門,一個叫做入口,另一個叫做出口。把一隻老鼠從一個無頂蓋的大盒子的入口處趕進迷宮。迷宮中設置很多隔壁,對前進方向形成了多處障礙,在迷宮的唯一出口處放置了一塊奶酪,吸引老鼠在迷宮中尋找通路以到達出口。求解迷宮問題,即找出從入口到出口的路徑。
實驗思路
考慮到迷宮的性質以及我們走迷宮的步驟,我們選擇回溯法來尋找通路,選擇棧這種數據結構用來保存一條完整的從入口到出口的路線。即從入口出發,按某一方向向前探索,若能走通(未走過的),即某處可以到達,則到達新點,否則試探下一方向 ; 若所有的方向均沒有通路,則沿原路返回前一點,換下一個方向再繼續試探,直到所有可能的通路都探索到,或找到一條通路,或無路可走又返回到入口點。
實驗內容和實驗步驟
需求分析
首先我們將迷宮設定爲 個空格組成的矩陣 **(也可以設置成n×m,但個人感覺意義不大)**在程序中,我用一個map[100][100]的數組來儲存地圖,接着我選擇用數字來表示地圖:0 代表一個空格,1 代表障礙,2 代表起點,3 代表終點。
其次,爲了滿足用戶需求,我提供用戶自行輸入地圖,和程序隨機生成地圖兩種模式。其中隨機生成地圖障礙和空格的比例大致爲1:10,用戶也可以自行調整。
最後,我們將找到的那一條從起點到終點的通路連同地圖打印出來。
概要設計
爲了表示一個座標x,y,我們選擇使用結構體來存儲一個座標點。爲了保存一條完整的路徑,我們自定義一個棧來存儲。
定義如下:
// 結構體表示座標
typedef struct {
int x;
int y;
}PointPos;
// 棧數據類型的定義
typedef struct
{
PointPos data[StackSize];
int top;
}SeqStack;
// 定義棧的主要功能
// 初始化一個棧
void InitStack(SeqStack &S);
// 判斷棧是否爲空
int StackEmpty(SeqStack S);
// 判斷棧是否爲滿
int StackFull(SeqStack S);
// 壓棧
void push(SeqStack &S, PointPos x);
// 彈出
PointPos pop(SeqStack &S);
// 返回棧頂元素
PointPos top(SeqStack S);
主程序的流程:請用戶選擇自行輸入還是隨機生成地圖 -> 確定地圖 -> 尋找起點和終點 -> 利用Stack實現深度優先搜索 -> 將結果打印在屏幕上
詳細設計以及代碼分析
用戶選擇
爲了實現用戶選擇的功能,我編寫了兩個函數。
// 用戶自行輸入函數
void Choose(int n)
{
printf("0 代表空格子,1 代表牆壁,2 代表起點,3 代表終點\n");
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> map[i][j];
}
// 隨機生成函數
void Random(int n)
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
// 設置空格和障礙的比例爲10:1
if (rand() % 10 == 1)
map[i][j] = 1;
else
map[i][j] = 0;
}
// 隨機設置起點和終點
int a, b, c, d;
a = rand() % n;
b = rand() % n;
c = rand() % n;
d = rand() % n;
map[a][b] = 2;
map[c][d] = 3;
printf("\n隨機生成的地圖是:\n");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
printf("%d ", map[i][j]);
}
printf("\n");
}
}
時間複雜度
尋找起點和終點
遍歷一遍地圖,記錄下起點和終點
void FindPos(PointPos& start, PointPos& end)
{
// 找到起點和終點座標
int f = 0;
SeqStack pointstack;
// 初始化棧
InitStack(pointstack);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (2 == map[i][j])
{
// 找到起點
start.x = i;
start.y = j;
f = f + 1;
}
else if (map[i][j] == 3)
{
// 找到終點
end.x = i;
end.y = j;
f = f + 1;
}
if (2 == f) break;
}
if (2 == f) break;
}
}
最壞時間複雜度
利用棧實現深度優先遍歷算法
深度優先搜索(DFS)(棧類實現),可以被形象的描述爲“打破沙鍋問到底”,具體一點就是訪問一個頂點之後,我繼續訪問它的下一個鄰接的頂點,如此往復,直到當前頂點被訪問或者它不存在鄰接的頂點。
具體思路如下:先從起點開始,依次尋找上下左右的座標,如果是障礙就跳過,如果不是障礙就把這個點入棧,並且標記下來,如此往復。與此同時,我給每個遍歷過的點增加一定的權值,在相同條件下,優先走權值大的點。如果找不到任何通路,就做一個標記,打印”沒有任何通路“。
在一定條件下,可以保證有曼哈頓距離的最優解。
具體代碼實現
void dfs(PointPos start, PointPos end)
{
// 定義存儲棧
SeqStack pointstack;
InitStack(pointstack);
// 標記路徑權值,定義點座標和臨時變量
int flag = 4;
PointPos pointpos, temp;
// 將起點座標入棧,並進入循環搜索
int cnt = 0;
push(pointstack, start);
while (!StackEmpty(pointstack))
{
cnt = 0;
pointpos = top(pointstack);
pop(pointstack);
// count記錄每個座標點有路徑的方向個數
int i, j, count = 0;
i = pointpos.x;
j = pointpos.y;
//如果爲搜索過的路徑,置爲權值
if (0 == map[i][j]) map[i][j] = flag;
// 座標點上下是否可走
if (i >= 0 && i <= n - 1)
{
if (i != 0 && 0 == map[i - 1][j])
{
temp.x = i - 1;
temp.y = j;
push(pointstack, temp);
++count;
}
else if (3 == map[i - 1][j] && i != 0)
{
cnt++;
break;
}
if (0 == map[i + 1][j] && i != n - 1)
{
temp.x = i + 1;
temp.y = j;
push(pointstack, temp);
++count;
}
else if (3 == map[i + 1][j] && i != n - 1)
{
cnt++;
break;
}
}
// 座標點左右是否可走
if (j >= 0 && j <= n - 1)
{
if (0 == map[i][j - 1] && j != 0)
{
temp.x = i;
temp.y = j - 1;
push(pointstack, temp);
++count;
}
else if (3 == map[i][j - 1] && j != 0)
{
cnt++;
break;
}
if (0 == map[i][j + 1] && j != n - 1)
{
temp.x = i;
temp.y = j + 1;
push(pointstack, temp);
++count;
}
else if (3 == map[i][j + 1] && j != n - 1)
{
cnt++;
break;
}
}
// 關於有多條通路的處理
if (1 != count)
++flag;
}
// check=1表示沒有通路
if (cnt == 0)
check = 1;
}
打印結果
通過棧已經將可能的路徑標記下來了,現在只需要輸出就可以。並且如果check=1的話,證明沒有通路,可以作爲特判條件。
具體代碼實現
void Output(PointPos start)
{
// 特判
if (check == 1)
{
printf("沒有一條合適的路徑\n");
return;
}
// 搜索到達的路徑
int i, j;
int ni = -1; int nj = -1;
int max;
i = start.x;
j = start.y;
max = map[i][j];
// 如果不是終點
while (3 != map[i][j])
{
// 上下判斷
if (i >= 0 && i <= n - 1)
{
if (map[i - 1][j] > max && i != 0)
{
ni = i - 1;
nj = j;
max = map[i - 1][j];
}
if (map[i - 1][j] == 3 && i != 0)
break;
if (map[i + 1][j] > max&& i != n - 1)
{
ni = i + 1;
nj = j;
max = map[i + 1][j];
}
if (map[i + 1][j] == 3 && i != n - 1)
break;
}
// 左右判斷
if (j >= 0 && j <= n - 1)
{
if (map[i][j - 1] > max&& j != 0)
{
ni = i;
nj = j - 1;
max = map[i][j - 1];
}
if (map[i][j - 1] == 3 && j != 0)
break;
if (map[i][j + 1] > max&& j != n - 1)
{
ni = i;
nj = j + 1;
max = map[i][j + 1];
}
if (3 == map[i][j + 1] && j != n - 1)
break;
}
// 標記爲路徑 即 -1
if (ni != -1 && nj != -1)
map[ni][nj] = -1;
else
{
// printf("沒有一條合適的路徑\n");
return;
}
// 設置初始最大值
max = 3;
// 進入迭代
i = ni;
j = nj;
}
// 輸出結果
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
// 標記爲路徑
if (-1 == map[i][j])
cout << '*' << ' ';
// 恢復原來的值
else if (map[i][j] > 3)
cout << 0 << ' ';
else
cout << map[i][j] << ' ';
}
cout << endl;
}
}
調試分析
調試過程中遇到的問題
- 邊界條件處理的不好。當i== 0 或者 i==n-1時 需要特殊判斷
- 一開始無法解決沒有通路該如何顯示結果。後來想到,當棧爲空時還沒有訪問到終點,證明沒有任何一條通路到達終點。這樣就能解決這個問題了。
- 地圖和棧的初始化問題,本來地圖初始化是零,但是因爲我設置的空格也是0,所以這樣就會出現問題。最後我把地圖初始化成了-1.
- 結果不正確。主要是本來應該存在通路的情況卻顯示沒有。通過我的調試,我發現是因爲我在特判時出現了問題。
break
語句和cnt++
語句搞反,導致還沒有自增1就退出了循環,debug了很久最終解決。
算法的時空分析
根據我的算法,打印地圖的時間複雜度爲。深度優先搜素算法時間複雜度爲,也就是所有邊的數量,,根據我的估算,時間複雜度大致爲。深度優先搜索算法空間複雜度爲也就是所有頂點的個數,爲。因此本算法,時空複雜度均爲
實驗結果
初始界面
選擇用戶輸入
需要輸入n
輸入地圖
打印有解情況
若是輸入無解
選擇隨機生成地圖
保證曼哈頓距離最短
實驗總結
- 記得break;要在語句之後,否則還沒來得及執行就退出循環了。
- DFS算法也可以用棧實現,但是BFS 算法必須要用到隊列這種數據結構。
- 在設置地圖時要控制好障礙和空格的比例,不然一旦點多了就無法找到結果。
- 在無解時,可以通過檢查棧爲空時是否到達終點來判斷。