這篇文章將會將一個數據結構與算法中一個很經典很重要的概念——深度優先搜索(Depth-First-Search:DFS)。。。。。。。。。(你他喵不是在標題里說了嗎?)
好吧,DFS的精髓我其實也還沒有弄的特別懂,估計得多用用才能理解更深吧。
!!!敲黑板!!!DFS的關鍵是遞歸,遞歸是真好用!!!
深度優先搜索(DFS)
什么是DFS呢,秉着能動手就絕不吵吵的原則,直接給出網上大神的博文鏈接:http://www.cnblogs.com/skywang12345/p/3711483.html
好吧,其實這種鏈接你自己都能百度到的。我就放這里做個參考,我的主要目的還是要講一下騎士周游問題。
騎士周游問題
騎士周游問題的主要內容是什么呢?不是廢話,直接上網址:http://blog.fishc.com/2554.html,這個頁面對DFS問題說明的非常清楚。作者是小甲魚大神,我在B站看的就是他的數據結構課程,不時的開個車差點閃了我的腰。
特喵的,不是說好的不說廢話嗎?(我錯了,好吧)
好吧,直接上代碼:
import datetime from enum import Enum class Size(Enum): X = 8 start = datetime.datetime.now() chess = [[0 for i in range(Size.X.value)]for j in range(Size.X.value)] def nextXY(x, y, position): global chess if position==0 and x-2>=0 and y-1>=0 and chess[x-2][y-1]==0: return [1, x-2, y-1] elif position==1 and x-2>=0 and y+1<=Size.X.value-1 and chess[x-2][y+1]==0: return [1, x-2, y+1] elif position==2 and x-1>=0 and y-2>=0 and chess[x-1][y-2]==0: return [1, x-1, y-2] elif position==3 and x-1>=0 and y+2<=Size.X.value-1 and chess[x-1][y+2]==0: return [1, x-1, y+2] elif position==4 and x+1<=Size.X.value-1 and y-2>=0 and chess[x+1][y-2]==0: return [1, x+1, y-2] elif position==5 and x+1<=Size.X.value-1 and y+2<=Size.X.value-1 and chess[x+1][y+2]==0: return [1, x+1, y+2] elif position==6 and x+2<=Size.X.value-1 and y-1>=0 and chess[x+2][y-1]==0: return [1, x+2, y-1] elif position==7 and x+2<=Size.X.value-1 and y+1<=Size.X.value-1 and chess[x+2][y+1]==0: return [1, x+2, y+1] else: return [0, x, y] def TravelChessBoard(x, y, tag): global chess chess[x][y] = tag if tag == Size.X.value**2: for i in chess: print(i) return "OK" f = 0 for i in range(8): flag = nextXY(x, y, i) if flag[0]: statues = TravelChessBoard(flag[1], flag[2], tag+1) if statues=="OK": return "OK" f += 1 else: f += 1 if f == 8: chess[x][y] = 0 print(TravelChessBoard(2, 0, 1)) print(datetime.datetime.now() - start)
整單代碼一共可以分為三個部分:變量准備部分,位置判斷部分,循環遞歸部分。
1.變量准備部分:
-
- 定義了一個size枚舉類,這是用於實現C語言中宏定義的功能特意定義的,當然你用別的方法也行,主要就是為了方便改變棋盤的規模。
- 定義了一個棋盤變量,是一個二維矩陣,全部元素初始化為0。也就是問題中的棋盤
2.位置判斷部分:
-
- 函數有三個參數,分別是坐標x, y和位置position, 這個位置position先來解釋一下,如下圖[1]所示,在國際象棋中,按照馬的走法(馬走日)對於任何一個位置,馬能走的地方一共有8個位置,每一個位置對應的坐標變換不一樣,這里的position對應的就是8種坐標變換方式。(x, y)就是馬當前所處的位置坐標
- 返回的參數為一個列表,列表中包含三個元素,第一個元素表示8個位置是否存在滿足條件的下一個位置,若存在,則第二、三兩個元素返回新位置的坐標,若不存在,返回原始坐標
- 判斷的條件主要就是同時滿足兩個方面:坐標不能出界,坐標對應的位置未曾走過(位置上的值為0),兩者都滿足即存在滿足條件的下一個位置
- 函數有三個參數,分別是坐標x, y和位置position, 這個位置position先來解釋一下,如下圖[1]所示,在國際象棋中,按照馬的走法(馬走日)對於任何一個位置,馬能走的地方一共有8個位置,每一個位置對應的坐標變換不一樣,這里的position對應的就是8種坐標變換方式。(x, y)就是馬當前所處的位置坐標
- 循環遞歸函數:
1 def TravelChessBoard(x, y, tag): 2 global chess 3 chess[x][y] = tag 4 if tag == Size.X.value**2: 5 for i in chess: 6 print(i) 7 return "OK" 8 f = 0 9 for i in range(8): 10 flag = nextXY(x, y, i) 11 if flag[0]: 12 statues = TravelChessBoard(flag[1], flag[2], tag+1) 13 if statues=="OK": 14 return "OK" 15 f += 1 16 else: 17 f += 1 18 if f == 8: 19 chess[x][y] = 0
-
- 傳進來的參數為當前馬所在的位置(x, y),以及當前走的是第幾步(tag的值)
- 第3行,chess[x][y] = tag, 另當前位置的值等於當前的步數,未到達的地方則為0,以此判斷一個位置是否到達過
- 第4-6行,如果步數等於棋盤的總格數(這里默認為方盤),則將棋盤打印出來,返回”OK“狀態,告訴上一層遞歸已經尋找到解了,無需再作其它搜索了
- 第8行,定義一個過程變量f = 0, 作用稍后會講到
- 第9-17行,對馬的當前位置的下8個位置進行遍歷,對於每一個位置,如果存在下個符合條件的位置則進入遞歸,將符合條件的下一個位置作為當前位置傳入遞歸函數中,步數加1。對8個位置遍歷,每次出現一個不符合條件的位置則f的值就加一。
- 第18-19行,當8個位置全部遍歷完,沒有一個符合條件的位置,那么此時f = 8,說明當前位置是不符合條件,那么就將當前位置的值重新置為0。
- 再說第12-15行,當深層的遍歷找不到合適的位置時,遞歸會退回到前一層,這說明前一層的當前位置也不符合條件,那么f的值就必須加1,然后繼續遍歷前一層的下一個位置,以此類推。
- 最后再說13-14行,當已經找到並打印出符合條件的路徑后,程序飯后”OK“狀態,此時程序的遞歸就會從最后一層不斷往前一層返回,為了加快程序結束的速度,就不繼續進行剩下的遍歷,每一層遞歸都直接返回可以了。如果不直接返回的話,程序會將所有的情況都遍歷完再返回,這樣時間就會非常非常非常長。YOU CAN HAVE A TRY !!!
到此這個程序也差不多講完了,我覺得只要理解遞歸了,這個程序應該不難理解。
連續幾天都在用遞歸,對遞歸的運用也越來越熟練,確實非常好用,真誠地希望這篇文章對你有幫助。
願各位學業有成!!!
[1] 圖拷貝自http://blog.fishc.com/2554.html,感謝魚C工作室。