貓和老鼠的迷宮問題


題目如下:

用一個10行10列的二維平面表格表示迷宮,左上角作為迷宮的入口,右下角作為迷宮的出口。設迷宮中有一只貓在隨機游走,一只老鼠要從迷宮的入口逃到出口。如果老鼠遇到貓就會被吃掉。假定老鼠和貓的速度是相同的,而且貓不會主動搜尋老鼠。問題求解的目標是老鼠尋找一條從入口到出口的通路,並且不會被貓吃掉,寫出問題的求解規則。(心形為老鼠初始位置,菱形為貓初始位置,五角星為迷宮出口)

 

這是人工智能導論課堂上老師留的一道作業題,課堂展示之后,為了不想讓自創的解題思路就這樣隱沒,於是決定浪費一些時間寫成博客,紀錄下來。由於從開始着手解決問題到編程實現只用了不到兩天的時候,所以一定會有算法漏洞以及邏輯冗余的情況。希望各路大神盡情指點,小輩一定認真學習。由於沒有細心地在網上查找有沒有相同的解題思路,所以如果雷同,純屬巧合。

第一眼看到題目的時候,本能反應是使用棧作為數據結構,再進行回溯來遍歷整個迷宮,但又隱隱覺得別扭。后來又由於個人懶散,直到課堂展示的前兩天才開始真正認真地考慮如何解決題目。

首先,為了解決這個問題,我們需要解決的疑點有二:

一、老鼠如何找到迷宮的出口;

二、如何讓老鼠躲避貓,不被抓到。

有貓沒貓,會對老鼠尋找出口的軌跡造成一定影響,但不會影響老鼠大體上的行走規則、行走指導思想。所以我們優先重點考慮問題一:老鼠如何找到迷宮的出口。

說起走迷宮,記得小時候玩仙劍奇俠傳的時候,遇到過迷宮,走起來很費勁……直到有一天父親對我說,走迷宮呀,貼着一邊的牆走就可以啦!這句話,我一直記得很清,為什么?是因為這句話對我的現實生活和玩游戲造成了很大的影響。那么對於這道題、這個老鼠,賦予它貼牆走的智慧,它完全就可以非常輕松地找到迷宮出口。所以我們該如何遍歷整體迷宮?我們用貼牆走來遍歷整個迷宮!(雖然貼牆走在本題中展現出良好的優越性,但是我個人也意識到這個算法有一定的缺陷,會在本篇的最后說明。)

在真正開始講解之前,大家需要認可和熟悉我們解決遍歷整個迷宮的基本算法規則,貼牆行走便可遍歷此圖。如果有對此規則保持迷惑,可以在詳解貼牆走規則后,親自使用地圖,嘗試遍歷整個迷宮后做近一步理解。在此題中,我們使用貼右牆走的方法,選擇右牆是因為比較符合我們個人習慣,但左右牆都可以實現遍歷整個地圖。

需要給大家說清楚的是,什么叫貼右牆走?怎么算是貼右牆走?如何做到貼右牆走?規則總結起來很簡單,那就是右手要始終挨着牆並且腳步要往前邁,不管遇到任何牆角任何路口,右手要始終挨着牆,這一點非常重要。

首先,假如我們處於這樣的四周環境下,如下圖所示(X代表牆,十字架中心代表我們現所在位置,空白表示是通路),北側和南側是牆,只有西側和東側是道路可以行走。(我將使用東南西北作為方位來描述,而不是前后左右,因為東西南北是絕對的,而前后左右是相對的,原因會在下面慢慢說明)

假如,我們現在面朝東,那么按照我們貼着右牆走的規則,下一步我們將會向東側行駛。但是如果我們現在面朝西呢?我們需要轉個身子過來,這時候我們再按照貼着右牆走的規則,我們將會向西側行駛。

為什么在同一種所處環境下,我們會走不同的方向?為什么換一個面朝方向,我們下一步將要走的格子就會有變化?這一切都歸於一個字,右。貼右牆、貼右牆,而右是一個相對位置,當我們面朝不同方向的時候,我們的右牆也不是同一堵牆。所以大家可以看到,即使在這相同環境下,但面朝方向的不同,對於下一步的選擇,我們有着不同的答案。

而大家更好理解的可能是,當我們面朝相同方向的時候,如都朝北。但因為所處環境的差異,我們將走向不同的方向。如下圖所示,當我們身處左側圖時,我們會選擇向西;而當我們身處右側圖時,同樣是面朝北,我們會繼續向北。所以可以簡單得出,即使面朝同一方向,但由於處在不同環境,對於下一步的選擇,我們依然有不同的選擇。

           

舉前面的例子,只是想告訴大家。在這個迷宮中我們會遇到各種各樣的情況,4種朝向、14種環境,相乘后便是56種不一樣情形。對待這56種情形,老鼠做出的判斷也應該是不同的(遵循貼右牆行駛的原則而做出的判斷)。那么我們該如何在程序中實現對待這56種情形做出相應的判斷呢?

首先,描述這56種情況中的任何一種都是相對不易的,可以自己想一下,我們的判斷條件中需要有四周圍牆的有無和老鼠的朝向。而如果直接用程序實現,程序會顯得異常冗長,所以我們只能尋找更好的手段來標識判斷這56種情況。

所以,我們想到了用數字標識,數字簡單清晰,一長串case語句便可將四分之一情況歸納總結,但是我們應該如何對每一種情況給定一個特定的數字?用1,2,3,4,5,6一直到56來標識這56種情況可不可以?一個數字一種情況,標識每個圖的問題解決了,但留下來的問題是老鼠怎么知道它現在所在的位置是多少號?可能第一步還因為程序里有老鼠初始位置的信息,知道自己是第17種情況,應該向右走(假設),但是第二步呢?程序總不能把老鼠要走的路徑全都存在程序里吧?那和直接把走向迷宮終點的路徑告訴小鼠,讓小鼠沿着路徑走過去有什么區別嗎?所以我們不能這樣簡簡單單的使用數字標識,而是需要用這些數字來滿足一些實時性的要求,也就是說,當老鼠處於這樣的環境、這樣的朝向時,它可以立刻求出用來標識的數字。

我們現在有兩個變量,一是老鼠面朝方向、二是四周牆面情況。我們可以認為這是一種稍微復雜的“映射”,通過變量一和變量二的計算,得到變量三,而變量三便是我們想要的標識數字。而我們的要求只有一點,那就變量三必須是唯一的,如果有兩對(變量一,變量二)計算,可以同時得到相同的變量三,那么變量三將失去作為標識的作用,同時我們也認為此種映射法則是失敗的。

於是,我們先對老鼠面朝方向賦予了權重,面朝北權重為1,面朝西權重為2,面朝南權重為3,面朝東權重為17,如下所示:

然后我們再對四周牆壁賦予了權重,北面牆權重為7,西面牆權重為11,南面牆權重為31,東面牆權重為37,如下圖所示:

於是自創公式:

weight_value = direction_value * (map[x-1][y] * W_NORTH + map[x][y - 1] * W_WEST+ map[x + 1][y] * W_SOUTH+ map[x][y + 1] * W_EAST)

其中direction_value為老鼠當前面朝方向的權重,W_NORTH,W_WEST,W_SOUTH,W_EAST分別為北牆、西牆、南牆、東牆各自的權值。由於此迷宮存儲在二維數組中,值為0認為是牆、值為1認為是通路,而坐標(x , y)是老鼠所在的中心坐標。故此自創“權值公式”中文含義是:老鼠面朝方向的權值乘以所有通路方向的權值之和得到的數字,是此情形(此環境,此朝向)的標識數字。

標識數字的選擇,如上文所說,我們只有一個要求,要求它的唯一性。所以我們對這8個數字的嚴格挑選,需要確保在56次計算得出來的數字沒有任意兩個相同。近十次的挑選8個數字的組合后,組合(1,2,3,17,7,11,31,37)符合我們要求,故我們使用這8個數字作為我們的權值,來時時刻刻標記老鼠的情形,並進一步做出判斷指導。如下圖便是,這些標識數字代表的是下一步應該向東西南北各個方向前進。

Ps:由於公式的自創性,故應該會存在更好的公式,更方便理解和計算,望大神們點撥。

 

根據計算,我們可以總結得出,當出現某些特定數字時,我們將向某特定方向行駛,於是我們貼右牆就有了完整的指導方針!所以直至現在,我們已經將問題一:老鼠如何找到迷宮出口成功解決,接下來,我們將繼續解決問題二:如何讓老鼠躲避貓?

我們需要解決的問題有兩個:

第一:如何不讓老鼠主動碰到貓。

第二:如何做到當老鼠遇到貓逃跑后,依然可以盡快找到迷宮出口。

對於第一個問題,我們將采用,認為貓是一堵牆的方法,來絕對避免老鼠主動碰到貓;與此同時,為了確保問題的簡單,我們同時認為老鼠也是一堵牆,貓絕不可能主動碰到老鼠。而對於貓和老鼠誰先走的問題,我們做出這樣的理解,我們賦予老鼠這樣的智慧:等到貓走了,我再走。我們需要強調的是,我們不是在刻意模糊題目,而是在賦予老鼠這樣的規則,來確保老鼠的安全(當然,如果把老鼠也當作一堵牆,貓是永遠不會撲向老鼠的。但在解題的初期,我們是有考慮如何避免貓碰到老鼠,由於后期覺得較復雜就舍棄掉了,但解釋還是這樣解釋)

對於第二個問題,我們將使用貼牆走的方式來作為逃跑方向的指導方針並采用棧來存儲遇到貓后逃跑的路線。使用什么方向並不重要,所以我們直接使用之前的算法作為逃跑方向的指導;而對於為什么使用棧來存儲逃跑路線,是因為棧的后進先出的特點,很符合我們現有的情況。既然我們從這里跑,那么我們還要回到這里,我們還要從遇到貓的這個地方繼續開始對迷宮進行遍歷,否則我們可能會恰好漏掉迷宮出口的位置而進行第二次對迷宮的遍歷。

大體的思路是這樣,但實際的程序邏輯卻比較復雜,在這里,我們簡單的表示一下邏輯關系。

 1 If(老鼠現在不在逃跑『也就是沒遇到貓』)
 2 
 3 {
 4 
 5    If(貓不在老鼠下一步的位置)
 6 
 7         老鼠按照貼牆規則正常前進;
 8 
 9    Else 『貓恰好擋住了老鼠的去路』
10 
11         將老鼠所在位置信息入棧,並繼續按照貼牆規則前進;
12 }
13 
14 Else 『老鼠現在正在逃跑』
15 
16 {
17 
18    If(貓繼續追過來了)
19 
20         將老鼠所在位置信息入棧,並繼續按照貼牆規則前進;
21 
22    Else『貓繼續追過來了貓不在棧頂所存信息的位置,可以簡單理解為貓沒有追來』
23 
24         老鼠按照棧頂信息回到上一步的位置,並彈棧;
25 
26 }

直到現在,我們已經成功的解決了貓與老鼠的迷宮問題,下面是C++的完成實現代碼。如果需要測試,需要在Do_it()函數中的while語句增添節點,通過一次debug便可實現貓和老鼠的一次移動。由於趕代碼太匆忙了,注解不足,同時代碼可能會有大量冗余,會在閑時慢慢修改,盡情諒解!

  1 #include<iostream>
  2 using namespace std;
  3 #include <stdlib.h>
  4 #include <time.h>
  5 
  6 #define ROW 10
  7 #define COL 10
  8 
  9 #define T_NORTH 1
 10 #define T_WEST 2
 11 #define T_SOUTH 3
 12 #define T_EAST 17
 13 
 14 #define W_NORTH 7
 15 #define W_WEST 11
 16 #define W_SOUTH 31
 17 #define W_EAST 37
 18 
 19 int map[ROW][COL] =
 20 {
 21     { 0,0,0,0,0,0,0,0,0,0 },
 22     { 0,1,1,1,1,0,1,1,1,0 },
 23     { 0,0,0,1,0,0,1,0,1,0 },
 24     { 0,1,1,1,0,1,1,0,0,0 },
 25     { 0,1,0,1,1,1,1,0,1,0 },
 26     { 0,1,0,1,1,0,1,1,1,0 },
 27     { 0,1,0,0,0,0,0,1,1,0 },
 28     { 0,1,1,1,1,0,1,0,1,0 },
 29     { 0,1,0,1,1,0,1,1,1,0 },
 30     { 0,0,0,0,0,0,0,0,0,0 },
 31 };
 32 
 33 int x = 1, y = 1;//老鼠位置坐標(x,y)    
 34 int cx = 4, cy = 6;//貓位置坐標(cx,cy)
 35 int cx0 = 4, cy0 = 6;
 36 
 37 
 38 void Display_Map()
 39 {
 40     for (int i = 0; i < ROW; i++)
 41     {
 42         for (int j = 0; j < COL; j++)
 43         {
 44             if (i == x&&j == y)
 45                 cout << "";
 46             else if (i == cx&&j == cy)
 47                 cout << "";
 48             else
 49             {
 50                 if (!map[i][j])
 51                     cout << "";
 52                 else
 53                     cout << "";
 54             }
 55         }
 56         cout << endl;
 57     }
 58 }
 59 
 60 void Cat_trail()
 61 {
 62     extern int cx, cy;
 63     extern int cx0, cy0;//貓之前的位置(cx0,cy0)
 64     int Cat_direction;
 65 
 66     srand((unsigned)time(NULL));
 67     //初始化隨機數種子  
 68 
 69     //cout << "(" << cx << "," << cy << ")" << endl;
 70 
 71     int Cat_judge = 0;
 72     while (!Cat_judge)         //產生一個隨機方向
 73     {
 74         Cat_direction = rand() % 4;
 75 
 76         switch (Cat_direction)
 77         {
 78         case 0:
 79             Cat_judge = map[cx - 1][cy];
 80             break;
 81         case 1:
 82             Cat_judge = map[cx][cy - 1];
 83             break;
 84         case 2:
 85             Cat_judge = map[cx + 1][cy];
 86             break;
 87         case 3:
 88             Cat_judge = map[cx][cy + 1];
 89             break;
 90         default:
 91             break;
 92         }
 93     }
 94 
 95     //下一步行走
 96     switch (Cat_direction)
 97     {
 98     case 0:
 99         cx = cx - 1;
100         break;
101     case 1:
102         cy = cy - 1;
103         break;
104     case 2:
105         cx = cx + 1;
106         break;
107     case 3:
108         cy = cy + 1;
109         break;
110     default:
111         break;
112     }
113 
114     map[cx0][cy0] = 1;//將貓剛走過的位置還原成0
115     cx0 = cx, cy0 = cy;
116     map[cx][cy] = 0;
117 
118 }
119 
120 void Do_it()
121 {
122     extern int x, y;
123     int x0 = 1, y0 = 1;//老鼠之前的位置(x0,y0)
124 
125     int direction_num = T_EAST;//方向數,北1,西2,南3,東17 
126 
127     int weight_num = 0;//位置(x,y)權值
128     int pre_weight_num = 0;
129     int escape[10][3] = { 0 };
130     int s_top = 0;
131 
132     int pre_Way_judge = 0;
133     int Way_judge = 0;
134     int Escape_judge = 0;
135 
136     while (x != 8 || y != 8)
137     {
138         map[x][y] = 0;//將老鼠在地圖上的位置標記
139         x0 = x, y0 = y;
140 
141         pre_weight_num = direction_num*(map[x - 1][y] * W_NORTH + map[x][y - 1] * W_WEST + map[x + 1][y] * W_SOUTH + map[x][y + 1] * W_EAST);//計算貓移動前分叉路口數
142         switch (pre_weight_num)
143         {
144             //下一步,向北行駛
145         case 36:case 18:case 306:case 150:
146         case 110:case 98:case 49:case 38:
147         case 76:case 88:case 119:case 21:
148         case 7:case 14:
149             pre_Way_judge = 0;
150             break;
151             //下一步,向西行駛
152         case 158:case 237:case 165:case 147:
153         case 126:case 42:case 84:case 144:
154         case 96:case 187:case 33:case 11:
155         case 22:case 54:
156             pre_Way_judge = 1;
157             break;
158             //下一步,向南行駛
159         case 1343:case 1275:case 225:case 833:
160         case 646:case 114:case 1156:case 714:
161         case 204:case 136:case 527:case 93:
162         case 31:case 62:
163             pre_Way_judge = 2;
164             break;
165             //下一步,向東行駛
166         case 79:case 75:case 55:case 935:
167         case 48:case 748:case 132:case 44:
168         case 816:case 68:case 629:case 111:
169         case 37:case 74:
170             pre_Way_judge = 3;
171             break;
172         }
173 
174         Cat_trail();
175 
176         int Cash_judge = 0;//是否碰撞
177         switch (pre_Way_judge)
178         {
179         case 0:
180             if (x - 1 == cx&&y == cy)
181                 Cash_judge = 1;
182             break;
183         case 1:
184             if (x == cx&&y - 1 == cy)
185                 Cash_judge = 1;
186             break;
187         case 2:
188             if (x + 1 == cx&&y == cy)
189                 Cash_judge = 1;
190             break;
191         case 3:
192             if (x == cx&&y + 1 == cy)
193                 Cash_judge = 1;
194             break;
195         }
196 
197         weight_num = direction_num*(map[x - 1][y] * W_NORTH + map[x][y - 1] * W_WEST + map[x + 1][y] * W_SOUTH + map[x][y + 1] * W_EAST);//計算貓移動后分叉路口數
198         switch (weight_num)
199         {
200             //下一步,向北行駛
201         case 36:case 18:case 306:case 150:
202         case 110:case 98:case 49:case 38:
203         case 76:case 88:case 119:case 21:
204         case 7:case 14:
205             Way_judge = 0;
206             break;
207             //下一步,向西行駛
208         case 158:case 237:case 165:case 147:
209         case 126:case 42:case 84:case 144:
210         case 96:case 187:case 33:case 11:
211         case 22:case 54:
212             Way_judge = 1;
213             break;
214             //下一步,向南行駛
215         case 1343:case 1275:case 225:case 833:
216         case 646:case 114:case 1156:case 714:
217         case 204:case 136:case 527:case 93:
218         case 31:case 62:
219             Way_judge = 2;
220             break;
221             //下一步,向東行駛
222         case 79:case 75:case 55:case 935:
223         case 48:case 748:case 132:case 44:
224         case 816:case 68:case 629:case 111:
225         case 37:case 74:
226             Way_judge = 3;
227             break;
228         }
229 
230         if (Escape_judge == 0)
231         {
232             if (!Cash_judge)
233             {
234                 switch (Way_judge)
235                 {
236                 case 0:
237                 {
238                     x = x - 1;
239                     direction_num = T_NORTH;
240                 }
241                 break;
242                 case 1:
243                 {
244                     y = y - 1;
245                     direction_num = T_WEST;
246                 }
247                 break;
248                 case 2:
249                 {
250                     x = x + 1;
251                     direction_num = T_SOUTH;
252                 }
253                 break;
254                 case 3:
255                 {
256                     y = y + 1;
257                     direction_num = T_EAST;
258                 }
259                 break;
260                 }
261 
262                 map[x0][y0] = 1;//將老鼠剛走過的位置還原成1
263 
264             }
265             else
266             {
267                 escape[s_top][0] = x;
268                 escape[s_top][1] = y;
269                 escape[s_top][2] = direction_num;
270 
271                 Escape_judge = 1;
272                 s_top++;
273 
274                 switch (Way_judge)
275                 {
276                 case 0:
277                 {
278                     x = x - 1;
279                     direction_num = T_NORTH;
280                 }
281                 break;
282                 case 1:
283                 {
284                     y = y - 1;
285                     direction_num = T_WEST;
286                 }
287                 break;
288                 case 2:
289                 {
290                     x = x + 1;
291                     direction_num = T_SOUTH;
292                 }
293                 break;
294                 case 3:
295                 {
296                     y = y + 1;
297                     direction_num = T_EAST;
298                 }
299                 break;
300                 }
301 
302                 map[x0][y0] = 1;//將老鼠剛走過的位置還原成1
303 
304             }
305         }
306         else
307         {
308             if (cx == escape[s_top - 1][0] && cy == escape[s_top - 1][1])
309             {
310                 escape[s_top][0] = x;
311                 escape[s_top][1] = y;
312                 escape[s_top][2] = direction_num;
313 
314                 Escape_judge = 1;
315                 s_top++;
316 
317                 switch (Way_judge)
318                 {
319                 case 0:
320                 {
321                     x = x - 1;
322                     direction_num = T_NORTH;
323                 }
324                 break;
325                 case 1:
326                 {
327                     y = y - 1;
328                     direction_num = T_WEST;
329                 }
330                 break;
331                 case 2:
332                 {
333                     x = x + 1;
334                     direction_num = T_SOUTH;
335                 }
336                 break;
337                 case 3:
338                 {
339                     y = y + 1;
340                     direction_num = T_EAST;
341                 }
342                 break;
343                 }
344 
345                 map[x0][y0] = 1;//將老鼠剛走過的位置還原成1
346 
347             }
348             else
349             {
350                 map[x][y] = 1;
351 
352                 s_top--;
353                 x = escape[s_top][0];
354                 y = escape[s_top][1];
355                 direction_num = escape[s_top][2];
356 
357                 if (0 == s_top)
358                     Escape_judge = 0;
359 
360             }
361         }
362         Display_Map();
363         cout << endl;
364     }
365 }
366 
367 int main()
368 {
369     Do_it();
370     return 0;
371 }

 

關於數學,關於計算機,還有太多值得自己去學習,希望以后可以在這里記錄下來。

如有錯誤或者建議,請盡情指教!

 

                                                                                                                                           大三上

                                                                                                                                    2016/10/19凌晨


免責聲明!

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



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