堆棧應用(六):迷宮搜索


1、問題描述

迷宮( m a z e)是一個矩形區域,它有一個入口和一個出口。在迷宮的內部包含不能穿越的牆或障礙。在圖 5 - 8所示的迷宮中,障礙物沿着行和列放置,它們與迷宮的矩形邊界平行。迷宮的入口在左上角,出口在右下角。圖5-8 迷宮假定用 n× m的矩陣來描述迷宮,位置 ( 1 , 1 )表示入口, (n,m) 表示出口, nm分別代表迷宮的行數和列數。迷宮中的每個位置都可用其行號和列號來指定。在矩陣中,當且僅當在位置(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 }
View Code

 

從文件中讀取:

 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 }
View Code

 

輸出迷宮:將牆壁用其他顏色標記

 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 }
View Code

 

 

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 }
View Code

 

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 }
View Code

 

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
View Code

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 }
View Code

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
View Code

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 }
View Code

測試程序:

堆棧的鏈式實現見:堆棧的鏈表方式實現

 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 }
View Code

 

輸出:此處使用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(迷宮中空閑的位置)

注意這個方法不保證找到的路徑是最短路徑,從結果也可以看出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM