更多數學趣題:走迷宮

===》點我返回目錄《===

我們都走過迷宮,在彎彎曲曲的小道上尋找出口,迷茫中夾帶驚喜。剛開頭都是亂走,碰到死衚衕後又折回來,慢慢地有了一套辦法,確保不遺漏可能的路徑,還有些人知道使用左手/右手法則。

我們用程序來走走迷宮。我們這兒定義的迷宮是簡單形式的,一個N*N矩形,四周有外牆,有一個入口,一個出口,保證有解,可以走的範圍從[0,0]到[N-1,N-1],假定[0,0]爲入口,[N-1,N-1]爲出口。

計算機裏面,我們可以採用不遺漏可能路徑的辦法,就是深度優先搜索。作爲預備知識,先介紹一下深度優先搜索和廣度優先的概念。

深度優先搜索DFS的思路是從起點開始,對每一個可能的分支路徑深入到不能再深入爲止。而廣度優先搜索BFS的思路是從起點開始,先把臨近的節點走完,再對臨近節點同樣走。比如:

 

我們對上面的圖,如何一個不漏地數出來?

按照深度優先,從a開始,接下來是臨近點b,然後對b同樣操作,到d,再到e。最後成爲一個串:abdeghicf。

按照廣度優先,從a開始,接下來是b,然後下一格臨近點c,這樣第一層的臨近點走完了,就對b和c同樣操作。最後成爲一個串:abcdefghi。

詳細的討論在講到數據結構的時候再說。

深度優先搜索是一種思路,保證一個不漏,其實是一種很笨的辦法,但是很有用,因爲經常會碰到沒有什麼好辦法的場景,我們就利用計算機的快進行窮舉搜索。我們具體編程也有不同的選擇,下面介紹遞歸的辦法進行實現。

我們先看怎麼定義迷宮。我們認爲迷宮是一個N*N格的矩形,有牆有空,我們可以用二維數組表格這些格,1表示牆,不能走,0表示空,可以走。假定左上角爲入口,右下角爲出口。

在Python中,定義二維數組不難,但是有小小的陷阱在其中。最直接的定義辦法是array2=[[0,0,0],[0,0,0],[0,0,0]],這樣給出一個3*3的二維數組。但是作爲一個有臉面的程序員,一般不會這麼寫程序,總是不想這麼寫死,而是想着用更加通用的方式。這個二維數組裏面的一維數組是[0,0,0],可以縮寫成array2=[[0]*3,[0]*3,[0]*3]。再看一下,這三個一維數組都是一樣的嘛,所以就會進一步縮寫成array2=[[0]*3]*3。

注意了,陷阱來了,上面看似定義好了一個3*3的二維數組,你現在給它賦值:array2[0][0]=1,再看二維數組內容,竟然成了:[[1, 0, 0], [1, 0, 0], [1, 0, 0]]。每一個一維數組的第一個數都成了1。這就要用引用與值來解釋了,一維數組本身是對象引用,*3會解釋成把[0,0,0]這個一維數組引用三遍,修改它的值,別的引用都變了。

正確的定義方式是array2= [ [0] * 3 for i in range(3)]。所以,我們這樣定義一個6*6的迷宮:

Mazewidth = 6

Maze = [[0] * (Mazewidth) for i in range(Mazewidth)]

我們程序還要用一個同樣的數組來記錄走過的路徑。Route = [[0] * (Mazewidth) for i in range(Mazewidth)]。我們反過來,用1表示走過的路徑。

深度優先很好理解,相當於用臨近節點替換當前節點繼續操作下去。過程爲:

(1) 從入口節點開始,設爲當前狀態;

(2) 按照一定次序(右下左上)選擇到下一個節點,新節點設爲當前狀態,遞歸;

(3) 判斷當前狀態是否和前面的重複,如果重複則回到上一個狀態,按照一定次序(右下左上)產生它的另一狀態;

(4) 判斷當前狀態是否爲目標狀態,如果是目標,則找到一個解答,結束算法。

我們用一個遞歸函數move(Maze,Route,i,j),Maze爲迷宮,Route爲走過的路徑,i,j爲當前節點。

按照我們的假定,右下角爲出口,判斷目標很簡單:

    if i == Mazewidth-1 and j == Mazewidth-1:

        Route[i][j] = 1

        return

下一個節點的選擇,按照右下左上,就是改變節點座標爲j+1,i+1,j-1,i-1。如探索右邊的格子,可以這樣做:

    if Route[i][j+1] == 0: #右邊的格子空

        Route[i][j+1] = 1 #記錄路徑

        j = j+1

        move(Maze,Route,i,j) #遞歸

自然,我們還不要忘記不能撞牆,還不要越界。所以提供如下一個函數:

def issafe(Maze,i,j):

    if i >= 0 and i < Mazewidth and j >= 0 and j < Mazewidth  and Maze[i][j] == 0:

        return 1

    return 0

把這些組合起來,得到如下走迷宮的函數:

def move(Maze,Route,i,j):

    if i == Mazewidth-1 and j == Mazewidth-1:

        Route[i][j] = 1

        for row in Route: #打印路徑

            print(' '.join([str(elem) for elem in row]))

        return

    if issafe(Maze,i,j+1) == 1 and Route[i][j+1] == 0: #right

        Route[i][j+1] = 1

        j = j+1

        move(Maze,Route,i,j)

    else:

        if issafe(Maze,i+1,j) == 1 and Route[i+1][j] == 0: #down

            Route[i+1][j] = 1

            i = i+1

            move(Maze,Route,i,j)

        else:

            if issafe(Maze,i,j-1) == 1 and Route[i][j-1] == 0: #left

                Route[i][j-1] = 1

                j = j-1

                move(Maze,Route,i,j)

            else:

                if issafe(Maze,i-1,j) == 1 and Route[i-1][j] == 0: #up

                    Route[i-1][j] = 1

                    i = i-1

                    move(Maze,Route,i,j)

                else:

                    return

好,程序完成了,測試一下:

Mazewidth = 6

Maze = [[0] * (Mazewidth) for i in range(Mazewidth)]

Route = [[0] * (Mazewidth) for i in range(Mazewidth)]



#Presetting the maze

Maze[1][0] = 1

Maze[1][1] = 1

Maze[1][2] = 1

Maze[1][3] = 1

Maze[1][4] = 1

Maze[2][3] = 1

Maze[2][4] = 1

Maze[3][1] = 1

Maze[4][1] = 1

Maze[4][2] = 1

Maze[4][3] = 1

Maze[4][4] = 1

Maze[4][5] = 1



for row in Maze:

    print(' '.join([str(elem) for elem in row]))

print('-----------')



Route[0][0] = 1



move(Maze,Route,0,0)

 

運行結果爲:

0 0 0 0 0 0

1 1 1 1 1 0

0 0 0 1 1 0

0 1 0 0 0 0

0 1 1 1 1 1

0 0 0 0 0 0

-----------

1 1 1 1 1 1

0 0 0 0 0 1

1 1 1 0 0 1

1 0 1 1 1 1

1 0 0 0 0 0

1 1 1 1 1 1

 

上面是迷宮,下面是走過的路徑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章