1、問題描述
迷宮( m a z e)是一個矩形區域,它有一個入口和一個出口。在迷宮的內部包含不能穿越的牆或障礙。在圖 5 - 8所示的迷宮中,障礙物沿着行和列放置,它們與迷宮的矩形邊界平行。迷宮的入口在左上角,出口在右下角。圖5-8 迷宮假定用 n× m的矩陣來描述迷宮,位置 ( 1 , 1 )表示入口, (n,m) 表示出口, n和m分別代表迷宮的行數和列數。迷宮中的每個位置都可用其行號和列號來指定。在矩陣中,當且僅當在位置(i,j)處有一個障礙時其值為 1 ,否則其值為 0。圖 5 - 9給出了圖 5 - 8中迷宮對應的矩陣描述。迷宮老鼠( rat in a maze)問題要求尋找一條從入口到出口的路徑。路徑是由一組位置構成的,每個位置上都沒有障礙,且每個位置(第一個除外)都是前一個位置的東、南、西或北的鄰居(如圖 5 - 1 0所示)。下面將要編寫程序來解決迷宮老鼠問題。假定程序中所使用的迷宮是正方形的(即行數等入口於列數) ,且迷宮足夠小,以便能在目標計算機的內存中描述整個迷宮。

2、設計
可以采用自頂向下的模塊化方法來設計這個程序。不難看出,這個程序可以划分為三個部分:輸入迷宮、尋找路徑和路徑輸出。
2.1 輸入迷宮
迷宮以矩陣的方式表示,輸入方式可以選擇讓用戶逐行輸入,也可以從文件讀取。
用戶逐行輸入:
1 void maze::InputMaze() 2 { 3 4 for (int rows = 1; rows < n + 1; ++rows) 5 { 6 int flag = 1; 7 cout << "請輸入第" << rows << "行:" << endl; 8 while (flag) 9 { 10 for (int cols = 1; cols < n + 1; ++cols) 11 { 12 cin >> mazem[rows][cols]; 13 } 14 cout << "輸入的第" << rows << "行為:"; 15 for (int cols = 1; cols < n + 1; ++cols) 16 { 17 cout << mazem[rows][cols] << " "; 18 } 19 20 if (mazem[1][1] == 1)//檢查入口 21 { 22 cout << "入口應沒有障礙物,請重新輸入。" << endl; 23 } 24 25 if (mazem[n][n] == 1)//檢查出口 26 { 27 cout << "出口應沒有障礙物,請重新輸入。" << endl; 28 } 29 cin.clear(); 30 cin.sync(); 31 cout << endl; 32 cout << "重新輸入請輸入1,繼續請輸入其他數字:" << endl; 33 34 int temp; 35 cin >> temp; 36 if (temp == 1) 37 { 38 flag = 1; 39 } 40 else 41 { 42 flag = 0; 43 } 44 } 45 46 47 } 48 49 cout << "輸入完畢,輸入的迷宮為:" << endl; 50 OutputMaze(); 51 }
從文件中讀取:
1 void maze::InputMazeFromFile(const std::string& filepath) 2 { 3 std::ifstream input; 4 input.open(filepath); 5 if (input) 6 { 7 for (int rows = 1; rows < n + 1;++rows) 8 { 9 for (int cols = 1; cols < n + 1;++cols) 10 { 11 if (input.eof()) 12 { 13 std::cerr << "迷宮數據不夠,請添加" << endl; 14 exit(1); 15 } 16 if (!(input >> mazem[rows][cols])) 17 { 18 std::cerr << "輸入有誤,迷宮應為數字" << endl; 19 exit(1); 20 } 21 22 } 23 } 24 } 25 else 26 { 27 std::cerr << "文件打開失敗" << endl; 28 exit(1); 29 } 30 input.close(); 31 OutputMaze(); 32 }
輸出迷宮:將牆壁用其他顏色標記
1 void maze::OutputMaze() 2 { 3 cout << "輸入的迷宮為:" << endl; 4 for (int rows = 1; rows < n + 1;++rows) 5 { 6 for (int cols = 1; cols < n + 1;++cols) 7 { 8 //顯示迷宮,牆壁用綠色顯示 9 if (mazem[rows][cols] == 1) 10 { 11 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_GREEN ); 12 } 13 else 14 { 15 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | BACKGROUND_RED); 16 17 } 18 cout << mazem[rows][cols]<<" "; 19 } 20 cout << endl; 21 } 22 23 cout << endl; 24 }
2.2、尋找路徑
思路:首先探討如何尋找迷宮路徑。首先把迷宮的入口作為當前位置。如果當前位置是迷宮出口,那么已經找到了一條路徑,搜索工作結束。如果當前位置不是迷宮出口,則在當前位置上放置障礙物,以便阻止搜索過程又繞回到這個位置。然后檢查相鄰的位置中是否有空閑的(即沒有障礙物) ,如果有,就移動到這個新的相鄰位置上,然后從這個位置開始搜索通往出口的路徑。如果不成功,選擇另一個相鄰的空閑位置,並從它開始搜索通往出口的路徑。為了方便移動,在進入新的相鄰位置之前,把當前位置保存在一個堆棧中。如果所有相鄰的空閑位置都已經被探索過,並且未能找到路徑,則表明在迷宮中不存在從入口到出口的路徑。
使用上述策略來考察圖 5 - 8的迷宮。首先把位置 ( 1 , 1 )放入堆棧,並從它開始進行搜索。由於位置( 1 , 1 )只有一個空閑的鄰居 ( 2 , 1 ),所以接下來將移動到位置 ( 2 , 1 ),並在位置 ( 1 , 1 )上放置障礙物,以阻止稍后的搜索再次經過這個位置。從位置 ( 2 , 1 )可以移動到 ( 3 , 1 )或( 2 , 2 )。假定移動到位置( 3 , 1 )。在移動之前,先在位置 ( 2 , 1 )上放置障礙物並將其放入堆棧。從位置 ( 3 , 1 )可以移動到( 4 , 1 )或( 3 , 2 )。假定移動到位置 ( 4 , 1 ),則在位置 ( 3 , 1 )上放置障礙物並將其放入堆棧。從位置 ( 4 , 1 )開始可以依次移動到 (5,1) 、 (6,1) 、 ( 7 , 1 )和( 8 , 1 )。到了位置 ( 8 , 1 )以后將無路可走。此時堆棧中包含的路徑從 ( 1 , 1 )至( 8 , 1 )。為了探索其他的路徑,從堆棧中刪除位置 ( 8 , 1 ),然后回退至位置( 7 , 1 ),由於位置 ( 7 , 1 )也沒有新的、空閑的相鄰位置,因此從堆棧中刪除位置 ( 7 , 1 )並回退至位置( 6 , 1 )。按照這種方式,一直要回退到位置 ( 3 , 1 ),然后才可以繼續移動(即移動到位置( 3 , 2))。注意在堆棧中始終包含從入口到當前位置的路徑。如果最終到達了出口,那么堆棧中的路徑就是所需要的路徑。
對於迷宮內部的位置(非邊界位置) ,有四種可能的移動方向:右、下、左和上。對於迷宮的邊界位置,只有兩種或三種可能的移動。為了避免在處理內部位置和邊界位置時存在差別,可以在迷宮的周圍增加一圈障礙物。對於一個m× n的迷宮,這一圈障礙物將占據數組 m a z e的第0行,第 m + 1 行,第 0列和第m + 1 列。
路徑查找:
1 bool maze::FindPath() 2 { 3 cout << "開始查找路徑..." << endl; 4 position current = { 1, 1 };//當前位置 5 6 mazem[1][1] = 3;//阻止返回入口 7 8 path = new LinkedStack<position>; 9 position offset[4]; 10 offset[0].row = 0; offset[0].col = 1;//右 11 offset[1].row = 1; offset[1].col = 0;//下 12 offset[2].row = 0; offset[2].col = -1;//左 13 offset[3].row = -1; offset[3].col = 0;//上 14 15 int option = 0; 16 int LastOption = 3; 17 while (current.row != n || current.col != n) 18 { 19 int r, c; 20 while (option <= LastOption) 21 { 22 r = current.row + offset[option].row; 23 c = current.col + offset[option].col; 24 if (mazem[r][c] == 0) break;//找到相鄰未查找過路徑 25 26 option++; 27 } 28 29 if (option <= LastOption) 30 { 31 path->Add(current);//將當前位置加入路徑 32 current.row = r; 33 current.col = c; 34 mazem[r][c] = 3;//防止再次探索,並方便后期復原為0,因此與牆壁的1區分 35 option = 0; 36 } 37 else//沒有路徑,回溯 38 { 39 if (path->IsEmpty())//沒有未探索的路徑,退出 40 { 41 return false; 42 } 43 position next; 44 path->Delete(next);//回溯到路徑的上個位置 45 if (next.row == current.row) 46 { 47 option = 2 + next.col - current.col; 48 } 49 else 50 { 51 option = 3 + next.row - current.row; 52 } 53 54 current = next; 55 } 56 57 } 58 59 cout << "查找完畢,如下圖." << endl; 60 ShowPath(); 61 return true; 62 }
2.3、輸出路徑
在這里,路徑用白色背景表示,其他部分與輸出矩陣時一樣。
1 void maze::ShowPath() 2 { 3 while (!path->IsEmpty()) 4 { 5 position temp; 6 path->Delete(temp); 7 mazem[temp.row][temp.col] = 2;//路徑元素改為2,便於區分 8 //cout << temp.row << " " << temp.col << endl; 9 } 10 mazem[n][n] = 2; 11 for (int rows = 1; rows < n + 1;++rows) 12 { 13 for (int cols = 1; cols < n + 1;++cols) 14 { 15 if (mazem[rows][cols]==2) 16 { 17 //改變路徑背景和字體顏色,紅字白背景 18 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); 19 20 } 21 else if (mazem[rows][cols] == 0 || (rows == n&&cols == n) || mazem[rows][cols] == 3) 22 { 23 mazem[rows][cols] =0;//將前面路徑查找過程中修改過的值復原 24 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | BACKGROUND_RED ); 25 } 26 else 27 { 28 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_GREEN); 29 } 30 cout << mazem[rows][cols] << " "; 31 } 32 33 cout << endl; 34 } 35 //恢復黑底白字 36 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY 37 | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 38 }
3、完整代碼
maze.h:
1 #ifndef MAZE_H 2 #define MAZE_H 3 #include "LinkedStack.h" 4 #include <fstream> 5 #include <cstdio> 6 #include<windows.h> 7 8 using std::cout; 9 using std::cin; 10 using std::endl; 11 12 struct position //表示位置的結構體 13 { 14 int row; 15 int col; 16 }; 17 18 19 class maze 20 { 21 public: 22 maze(int m); 23 maze(int* matrix, int m);//直接用矩陣初始化 24 ~maze(); 25 bool FindPath();//尋找從入口到出口的路徑 26 void InputMaze();//逐行輸入迷宮 27 void InputMazeFromFile(const std::string& filepath);//從文件讀取 28 void OutputMaze();//輸出迷宮地圖 29 void ShowPath();//輸出路徑 30 private: 31 LinkedStack<position> *path;//存儲路徑的堆棧 32 int **mazem;//迷宮矩陣 33 int n;//迷宮大小 34 }; 35 36 37 #endif
maze.cpp:
1 #include "maze.h" 2 maze::maze(int m) :n(m) 3 { 4 mazem = new int*[n + 2]; 5 for (int i = 0; i < n + 2; ++i) 6 { 7 mazem[i] = new int[n + 2]; 8 } 9 for (int i = 0; i < n + 2;++i) 10 { 11 mazem[0][i] = 1; 12 mazem[i][0] = 1; 13 mazem[n + 1][i] = 1; 14 mazem[i][n + 1] = 1; 15 } 16 17 } 18 19 maze::maze(int* matrix, int m):n(m) 20 { 21 mazem = new int*[n + 2]; 22 for (int i = 0; i < n + 2; ++i) 23 { 24 mazem[i] = new int[n + 2]; 25 } 26 for (int i = 0; i < n + 2; ++i) 27 { 28 mazem[0][i] = 1; 29 mazem[i][0] = 1; 30 mazem[n + 1][i] = 1; 31 mazem[i][n + 1] = 1; 32 } 33 34 for (int rows = 1; rows < n + 1;++rows) 35 { 36 for (int cols = 1; cols < n + 1;++cols) 37 { 38 mazem[rows][cols] = *(matrix+(rows - 1)*m+cols-1); 39 } 40 } 41 } 42 43 maze::~maze() 44 { 45 if (mazem != NULL) 46 { 47 for (int i = 0; i < n + 2; ++i) 48 { 49 if (mazem[i] != NULL) 50 { 51 delete[] mazem[i]; 52 } 53 } 54 55 delete[] mazem; 56 } 57 } 58 59 bool maze::FindPath() 60 { 61 cout << "開始查找路徑..." << endl; 62 position current = { 1, 1 };//當前位置 63 64 mazem[1][1] = 3;//阻止返回入口 65 66 path = new LinkedStack<position>; 67 position offset[4]; 68 offset[0].row = 0; offset[0].col = 1;//右 69 offset[1].row = 1; offset[1].col = 0;//下 70 offset[2].row = 0; offset[2].col = -1;//左 71 offset[3].row = -1; offset[3].col = 0;//上 72 73 int option = 0; 74 int LastOption = 3; 75 while (current.row != n || current.col != n) 76 { 77 int r, c; 78 while (option <= LastOption) 79 { 80 r = current.row + offset[option].row; 81 c = current.col + offset[option].col; 82 if (mazem[r][c] == 0) break;//找到相鄰未查找過路徑 83 84 option++; 85 } 86 87 if (option <= LastOption) 88 { 89 path->Add(current);//將當前位置加入路徑 90 current.row = r; 91 current.col = c; 92 mazem[r][c] = 3;//防止再次探索,並方便后期復原為0,因此與牆壁的1區分 93 option = 0; 94 } 95 else//沒有路徑,回溯 96 { 97 if (path->IsEmpty())//沒有未探索的路徑,退出 98 { 99 return false; 100 } 101 position next; 102 path->Delete(next);//回溯到路徑的上個位置 103 if (next.row == current.row) 104 { 105 option = 2 + next.col - current.col; 106 } 107 else 108 { 109 option = 3 + next.row - current.row; 110 } 111 112 current = next; 113 } 114 115 } 116 117 cout << "查找完畢,如下圖." << endl; 118 ShowPath(); 119 return true; 120 } 121 122 void maze::InputMaze() 123 { 124 125 for (int rows = 1; rows < n + 1; ++rows) 126 { 127 int flag = 1; 128 cout << "請輸入第" << rows << "行:" << endl; 129 while (flag) 130 { 131 for (int cols = 1; cols < n + 1; ++cols) 132 { 133 cin >> mazem[rows][cols]; 134 } 135 cout << "輸入的第" << rows << "行為:"; 136 for (int cols = 1; cols < n + 1; ++cols) 137 { 138 cout << mazem[rows][cols] << " "; 139 } 140 141 if (mazem[1][1] == 1)//檢查入口 142 { 143 cout << "入口應沒有障礙物,請重新輸入。" << endl; 144 } 145 146 if (mazem[n][n] == 1)//檢查出口 147 { 148 cout << "出口應沒有障礙物,請重新輸入。" << endl; 149 } 150 cin.clear(); 151 cin.sync(); 152 cout << endl; 153 cout << "重新輸入請輸入1,繼續請輸入其他數字:" << endl; 154 155 int temp; 156 cin >> temp; 157 if (temp == 1) 158 { 159 flag = 1; 160 } 161 else 162 { 163 flag = 0; 164 } 165 } 166 167 168 } 169 170 cout << "輸入完畢,輸入的迷宮為:" << endl; 171 OutputMaze(); 172 } 173 174 void maze::InputMazeFromFile(const std::string& filepath) 175 { 176 std::ifstream input; 177 input.open(filepath); 178 if (input) 179 { 180 for (int rows = 1; rows < n + 1;++rows) 181 { 182 for (int cols = 1; cols < n + 1;++cols) 183 { 184 if (input.eof()) 185 { 186 std::cerr << "迷宮數據不夠,請添加" << endl; 187 exit(1); 188 } 189 if (!(input >> mazem[rows][cols])) 190 { 191 std::cerr << "輸入有誤,迷宮應為數字" << endl; 192 exit(1); 193 } 194 195 } 196 } 197 } 198 else 199 { 200 std::cerr << "文件打開失敗" << endl; 201 exit(1); 202 } 203 input.close(); 204 OutputMaze(); 205 } 206 207 void maze::OutputMaze() 208 { 209 cout << "輸入的迷宮為:" << endl; 210 for (int rows = 1; rows < n + 1;++rows) 211 { 212 for (int cols = 1; cols < n + 1;++cols) 213 { 214 //顯示迷宮,牆壁用綠色顯示 215 if (mazem[rows][cols] == 1) 216 { 217 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_GREEN ); 218 } 219 else 220 { 221 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | BACKGROUND_RED); 222 223 } 224 cout << mazem[rows][cols]<<" "; 225 } 226 cout << endl; 227 } 228 229 cout << endl; 230 } 231 232 void maze::ShowPath() 233 { 234 while (!path->IsEmpty()) 235 { 236 position temp; 237 path->Delete(temp); 238 mazem[temp.row][temp.col] = 2;//路徑元素改為2,便於區分 239 //cout << temp.row << " " << temp.col << endl; 240 } 241 mazem[n][n] = 2; 242 for (int rows = 1; rows < n + 1;++rows) 243 { 244 for (int cols = 1; cols < n + 1;++cols) 245 { 246 if (mazem[rows][cols]==2) 247 { 248 //改變路徑背景和字體顏色,紅字白背景 249 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); 250 251 } 252 else if (mazem[rows][cols] == 0 || (rows == n&&cols == n) || mazem[rows][cols] == 3) 253 { 254 mazem[rows][cols] =0;//將前面路徑查找過程中修改過的值復原 255 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | BACKGROUND_RED ); 256 } 257 else 258 { 259 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | BACKGROUND_GREEN); 260 } 261 cout << mazem[rows][cols] << " "; 262 } 263 264 cout << endl; 265 } 266 //恢復黑底白字 267 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY 268 | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 269 }
welcome.h:
1 #ifndef WELCOME_H 2 #define WELCOME_H 3 #include <iostream> 4 using std::cout; 5 using std::endl; 6 using std::cin; 7 void Welcome(int& n); 8 #endif
welcome.cpp:
1 #include "welcome.h" 2 3 void Welcome(int& n) 4 { 5 cout << "歡迎來到迷宮游戲!" << endl; 6 cout << "本程序只能輸入方陣" << endl; 7 cout << "請輸入迷宮的行數" << endl; 8 9 cin >> n; 10 }
測試程序:
堆棧的鏈式實現見:堆棧的鏈表方式實現
1 #include "welcome.h" 2 #include "maze.h" 3 4 int main() 5 { 6 7 int n;//迷宮行數 8 Welcome(n); 9 int flag; 10 cout << "請選擇輸入迷宮的方式:" << endl; 11 cout << "1.手動逐行輸入" << endl; 12 cout << "2.從文件讀取" << endl; 13 cin.sync(); 14 while(!(cin>>flag)) 15 { 16 std::cerr << "error:只能輸入1,2選擇,請重新選擇" << endl; 17 cin.clear(); 18 cin.sync(); 19 } 20 21 maze m(n); 22 23 if (flag==1) 24 { 25 m.InputMaze(); 26 } 27 else 28 { 29 m.InputMazeFromFile("..\\Debug\\maze.txt"); 30 } 31 /* 32 int matrix[8][8] = 33 { 34 0, 0, 0, 0, 1, 0, 0, 0, 35 0, 1, 0, 0, 1, 0, 0, 0, 36 0, 1, 0, 0, 1, 0, 0, 0, 37 0, 1, 0, 0, 0, 1, 0, 0, 38 0, 0, 0, 1, 0, 0, 1, 0, 39 0, 0, 0, 1, 1, 0, 0, 0, 40 0, 0, 0, 0, 0, 0, 1, 0, 41 0, 0, 0, 0, 0, 0, 0, 0, 42 43 }; 44 */ 45 46 //maze m((int*)matrix,8); 47 //m.OutputMaze(); 48 if (!m.FindPath()) 49 { 50 cout << "沒有從入口到出口的路徑!" << endl; 51 52 } 53 54 55 56 system("pause"); 57 58 return 0; 59 }
輸出:此處使用maze.txt作為輸入,按行存儲迷宮,元素之間空格隔開
輸入為:
0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 1 0 0 0
0 1 0 0 0 0 1 0 0 0
0 1 1 1 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0
0 0 1 0 0 1 0 1 1 0
0 0 1 0 0 1 0 0 0 0
0 0 1 1 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 0


算法復雜度:O(迷宮中空閑的位置)
注意這個方法不保證找到的路徑是最短路徑,從結果也可以看出。
