問題描述
給定一張迷宮地圖和一個迷宮入口,然后進入迷宮探索找到一個出口。如下圖所示:
該圖是一個矩形區域,有一個入口和出口。迷宮內部包含不能穿越的牆壁或者障礙物。這些障礙物沿着行和列放置,與迷宮的邊界平行。迷宮的入口在左上角,出口在右下角。
問題分析
-
首先要有一張迷宮地圖,地圖由兩部分組成:
(1)一是迷宮中各處的位置坐標,
(2)二是迷宮各位置處的狀態信息,即該處是牆還是路
所以,該迷宮地圖可由一個二維數組來表示。數組的橫縱坐標表示迷宮各處的位置坐標,數組元素表示各位置處的狀態信息。
2.在這里,假定:
(1)迷宮地圖是m*n的,即二維數組是m行n列的。
(2)在迷宮中用1表示牆,用0表示路。當然,為了便於標識,我們后面還會用其他數字代表更多含義。
因此,迷宮的地圖一個刻畫如下:
現在我們要找一條從入口到出口的路徑。路徑是一個由位置組成的序列,每一個位置都沒有障礙,並且除入口外,路徑上的每一個位置都是前一個位置在東西南北方向上相鄰的一個位置。
不過,考慮到邊界問題不太好處理。我們做這樣的工作,在地圖外圍加一層圍牆,給它全部填上1。這樣,在處理各個坐標時,都沒有差別了。這樣一來便大大簡化了我們的工作。
下面我們要編寫程序求解這個問題。
程序設計
這次還是采用一個簡單的模塊化來設計這個程序。那么主要有下面幾個模塊:
- 顯示歡迎信息
- 初始化工作
- 生成地圖
- 找路
- 打印地圖和路徑
下面我們分別完成這些功能。
顯示歡迎信息
這個模塊就很簡單了,輸出一些信息提醒使用者就行,主要是為了增加程序的友好性而設置的。大家根據自己的需要自行發揮。例如我的就很隨便了:
1void welcome()
2{
3 cout << "welcome to RAT IN MAZE" << endl;
4 system("pause");
5 system("cls");
6}
初始化工作
這個主要是設置一些全局變量的取值和完成內存的分配,地圖的存儲還是從堆上分配內存比較好。因為一般來說,考慮到地圖可能會很大,這樣需要的存儲空間就很多了。在這里一並把相關的全局變量給講解了吧。
1typedef struct
2{
3 int row;
4 int col;
5}POSITION;
6
7
8const POSITION maze_size = { 20 , 60 };
9
10int ** const maze = new int*[maze_size.row + 2];
11
12stack<POSITION> path;
13POSITION offset[4];//direction
-
POSITION結構體
坐標結構體變量類型,很容易理解,有兩個成員變量:x坐標和y坐標。 -
maze_size
定義地圖的大小,實際分配內存的時候,我們還需要考慮地圖邊界也需要存儲空間。總之,我們的地圖坐標范圍是1 to maze_size。 -
maze
二位數組,存儲地圖,分配的時候+2是用來存儲邊界的。至於const則是約束指針不改變。不過我們的地圖數組是根據maze_size大小動態分配的。 -
path
用來存路徑的。 -
offset
用來設置位置偏移的。比如我們當前位置是(row = 1, col = 1),那么通過row + 1便可往下走,row - 1就是往上走。col + 1往右走,col - 1 往左走。等等。通過坐標加減offset偏移,便可以移動了。
1void init()
2{
3 //偏移
4 offset[0].row = 0; offset[0].col = 1; //right
5 offset[1].row = 1; offset[1].col = 0; //down
6 offset[2].row = 0; offset[2].col = -1; //left
7 offset[3].row = -1; offset[3].col = 0; //up
8
9 //maze = new int*[maze_size.row + 2];
10 for (int i = 0; i < maze_size.row + 2; i++)
11 {
12 maze[i] = new int[maze_size.col + 2];
13 }
14}
這個代碼就是設置偏移的數值,以及動態分配地圖數組了。
生成地圖
生成地圖還是根據地圖尺寸,然后隨機設置障礙。不過要注意障礙出現的概率設置得小一點,不然地圖一般無解。可以用rand()隨機數來做。這一步也要把圍牆設置好。
1//地圖范圍1 - maze_size 有圍牆
2void randomMaze()
3{
4 int i, j, rate;
5
6 for (i = 0; i < maze_size.row + 2; i++)
7 {
8 for (j = 0; j < maze_size.col + 2; j++)
9 {
10 //設置圍牆
11 if ((i == 0) || (i == maze_size.row + 1) || (j == 0) || (j == maze_size.col + 1))
12 {
13 maze[i][j] = 1;
14 }
15 else
16 {
17 rate = rand() % 10+1;
18 if (rate <= 3)
19 {
20 maze[i][j] = 1;//隨機生成障礙
21 }
22 else
23 {
24 maze[i][j] = 0;
25 }
26 }
27 }
28 }
29 //最后保證起點和終點能走
30 maze[1][1] = maze[maze_size.row][maze_size.col] = 0;
31}
找路
這個是整個程序設計的核心功能,沒有之一。在寫代碼之前,我們需要弄明白,到底怎么找路呢?
- 首先,把迷宮入口作為當前位置。
- 如果當前位置是迷宮出口,那么已經找到一條路徑了,程序結束。
- 如果當前位置不是出口,則在當前位置放置障礙物,表示這里已經來過,防止下次又重復繞回來。然后檢查相鄰位置是否能走。
- 如果一個相鄰位置能走,就移動到這個位置上。然后在新的位置上重新開始尋找出口。如果不能走,就嘗試下一個相鄰位置。
- 如果所有的相鄰位置都不能走了,則回退到上一個位置,重新選擇上一個位置的其他相鄰位置,繼續探索。
- 如果所有的相鄰位置都被探索過了,仍然找不到路徑,那說明這個迷宮不存在這樣的路徑。
例如,下面的地圖:
圖8-9
好了,說了這么多,相信大家已經了解得差不多,並且躍躍欲試了。
1bool findPath()
2{
3 POSITION here; //當前位置
4 here.row = here.col = 1;
5 maze[1][1] = 3; //放置障礙,防止回來
6 int option = 0; //next step
7 const int lastOption = 3;
8
9 //find a path
10 while ( here.row != maze_size.row || here.col != maze_size.col)
11 {
12 //not reach the end
13 int r, c;
14 while (option <= lastOption)
15 {
16 r = here.row + offset[option].row;
17 c = here.col + offset[option].col;
18 if (maze[r][c] == 0)
19 {
20 break;
21 }
22 option++;//next choice
23 }
24
25 //相鄰的位置能走?
26 if (option <= lastOption)
27 {
28 path.push(here);
29 here.row = r;
30 here.col = c;
31 maze[r][c] = 3; //走過了
32 option = 0;
33 }
34 else
35 {
36 if (path.empty())
37 {
38 return false;
39 }
40 //go back
41 maze[here.row][here.col] = 3; //此路不可通
42 here = path.top();
43 path.pop();
44 option = 0;
45 }
46 }
47
48 maze[maze_size.row][maze_size.col] = 2;
49
50 return true;
51}
相關代碼如上面所示,結合前面的講解,相信大家也能看懂。
打印地圖和路徑
這個功能就比較簡單了,主要是根據maze的信息,生成相應的地圖顯示出來給大家直觀的看到。對於maze里面存的數值,我們也可以作一個小小的規定:
- 0
表示位置可通行。 - 1
表示位置有障礙。 - 2
表示該位置處在找到的路徑上面。 - 3
探索過程中放置的障礙物。這個障礙物和1表示的障礙物不同的是,這個障礙我們放置的,和生成地圖時的固定障礙物不同。因此還是要區分開來的。
然后打印的時候,遍歷maze數組,遇到:
- 0
打印空格。 - 1
打印*號。 - 2
打印#號,並且設置下顏色。 - 3
還是打印空格。(或者根據自己的喜好打印另外的符號,這樣就可以把探索過的所有位置顯示出來。)
最后在打印最終的地圖和路徑之前,如果找到一條路徑。我們還要根據path中的路徑,在maze里面設置好相應的值,才做最終的輸出:
1void setPathOnMaze()
2{
3 POSITION pos;
4 while (!path.empty())
5 {
6 pos = path.top();
7 path.pop();
8 maze[pos.row][pos.col] = 2;//路徑
9 }
10}
11
12然后,可以開始輸出我們的地圖了。
具體代碼:
1void outputMaze()
2{
3 int i, j;
4 for (i = 0; i < maze_size.row + 2; i++)
5 {
6 for (j = 0; j < maze_size.col + 2; j++)
7 {
8 if (maze[i][j] == 1)
9 {
10 cout << "*";
11 }
12 else if ((maze[i][j] == 0) || (maze[i][j] == 3))
13 {
14 cout << " ";
15 }
16 else
17 {
18 HANDLE hOut;
19 hOut = GetStdHandle(STD_OUTPUT_HANDLE);
20 SetConsoleTextAttribute(hOut,FOREGROUND_GREEN | FOREGROUND_INTENSITY);
21 cout << "#";
22 SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
23 }
24 }
25 cout << endl;
26 }
27}
最終效果
-
沒有找到路的情況:
沒有找到路 -
找到了路徑:
找到了路徑
代碼獲取
欲獲取代碼,請關注我們的微信公眾號【程序猿聲】,在后台回復:rat。即可下載。
微信公眾號
