深度優先搜索(DFS)是一種基本的圖算法,主要針對圖和樹,英文縮寫:DFS。
深度優先搜索使用的策略:從一個頂點V0開始,沿着一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然後從另一條路開始走到底。
理解深度優先搜索的關鍵在於:解決“當下該如何做”,同時“下一步如何做”則和“當下該如何做”是一樣的。
通常的方法:把每一種可能都去嘗試一遍(一般使用for循環來遍歷),當前這一步解決後便進入下一步。下一步的解決方法和當前這步的解決方法是完全一樣的。
- 在訪問其中一個頂點時,將它標記爲已訪問
- 遞歸地訪問它的所有沒有被標記過的鄰居頂點。
【深度優先搜索算法要點】
⑴定義狀態。
即如何描述問題求解過程中每一步的狀況。在n皇后問題中,將行l位置a[l]作爲狀態。如果擴展結點時參與運算的變量有多個,爲了精簡程序,增加可讀性,我們一般將參與子結點擴展運算的變量組合成當前狀態列入值參,以便回溯時能恢復遞歸前的狀態,重新計算下一條路徑。
⑵邊界條件。
即在什麼情況下程序不再遞歸下去。在n皇后問題中,將l=n+1(產生一種成功擺法)作爲邊界條件。如果是求滿足某個特定條件的一條最佳路徑,則當前狀態到達邊界時並不一定意味着此時就是最佳目標狀態。因此還須增加判別最優目標狀態的條件。
⑶搜索範圍
在當前狀態不滿足條件的情況下,應如何設計搜索的範圍。換句話說,如何設定for語句中循環變量的初值和終值;在n皇后問題中,l行的列位置i作爲搜索範圍,即l<=i<=n。
⑷約束條件和最優要求。
所謂約束條件是指,當前擴展出一個子結點後應滿足什麼條件方可繼續遞歸下去;是求滿足某個特定條件的一條最佳路徑,那麼在擴展出某個子狀態後是否滿足最優性要求。在n皇后問題中,將(l,i)置放皇后不產生攻擊(att=false)作爲約束條件。
⑸參與遞歸運算的參數。
將參與遞歸運算的參數設爲遞歸子程序的值參或局部變量。若這些參數的存儲量大(例如數組)且初始值需由主程序傳入,爲避免內存溢出,則必須將其設爲全局變量,且回溯前需恢復其遞歸前的值,在n皇后問題中,將皇后的行位置l和列位置i作爲參與遞歸運算的參數。
【走迷宮】
深度優先搜索的重要例子——走迷宮。
用迷宮代替圖,通道代替邊,路口代替定點,那麼我們就可以將迷宮看成是一個圖。
要探索迷宮中的所有通道,我需要這樣做:(和DFS思想是一樣的)
- 選擇一條沒有標記過的通道,在走過的路上鋪一條繩子
- 標記所有第一次路過的路口和通道
- 當遇到一個標記過的路口時回退到上一個路口
- 當回退到的路口已經沒有可走的通道時繼續回退
圖例:
【題目】
迷宮由n行m列的單元格組成,每個單元格要麼是空地,要麼是障礙物,需要找到一條從起點通往終點的最短路徑。
注意:障礙物不能走,不能走到迷宮外面。
起點 |
|
障礙 |
|
|
|
|
|
|
|
障礙 |
|
|
障礙 |
終點 |
|
|
|
|
障礙 |
起點爲(1,1),只能往下走或往右走,我們一個一個進行嘗試。
- 先往右走,直到走不通的時候再往回回退,然後再去嘗試另外一個方向。
- 起點(1,1)一步之內可以到達的點爲(1,2)和(2,1),先往右走,到達(1,2)。
- 到達(1,2)後來判斷又能到達哪些點?只有(2,2),然後繼續往下走,直到無路可走或到達終點爲止。
下圖是一種可行的搜索路徑:
1 |
2 |
障礙 |
|
|
3 |
4 |
5 |
|
|
障礙 |
6 |
|
障礙 |
終點8 |
7 |
|
|
|
障礙 |
【DFS】
DFS函數的功能是解決當前應該怎麼辦。
在這道題目時,這一步需要解決的是:檢查是否已經到了終點,如果沒有到達終點則找出下一步可以走的地方。
因此,這裏的DFS()需要維護3個參數,當前點的x座標,y座標,已經走過的步數step。如下
def dfs(x,y,step):
{
return 0
}
檢查是否已經到達終點,只需要判斷當前座標和終點座標是否相等就可以了,如果相等則表明已經到達終點的位置。
def dfs(x,y,step):
{
#判斷是否到達終點位置
if x == p and y == q:
#更新最小值
if step < min
min = step
return
return 0
}
如果沒有到達終點,則找出下一步可以走的地方。但是有四個方向可以走,我先定義一個方向數組
next_step = [[0,1], #向右走
[1,0], #向下走
[0,-1], #向左走
[-1,0] #向上走
]
通過這個方向數組,使用循環就可以得到下一步的座標。
for i in range(len(next_step)):
next_x = start_x + next_step[i][0]
next_y = start_y + next_step[i][1]
接下來,要對下一個點進行判斷,包括是否越界,是否爲障礙物,是否這個點已經在路徑中(使用book[next_x][next_y]來記錄當前點是否已經在路徑中)
如果這個點符合所有的要求,就對這個點進行下一步,dfs(next_x,next_y,step+1).
def dfs(start_x,start_y,end_x,end_y,migong_array,step):
'''
:param start_x: 起始橫座標
:param start_y: 起始縱座標
:param end_x: 終點橫座標
:param end_y: 終點縱座標
:param migong_array: 迷宮的數組
:return:
'''
next_step = [[0,1], #向右走
[1,0], #向下走
[0,-1], #向左走
[-1,0] #向上走
]
if (start_x == end_x and start_y == end_y):
global MIN
if(step < MIN):
MIN = step
return
for i in range(len(next_step)):
next_x = start_x + next_step[i][0]
next_y = start_y + next_step[i][1]
# 判斷是否越界
if(next_x < 0 or next_y < 0 or next_x > len(migong_array) or next_y > len(migong_array[0])):
continue
# 判斷是否爲障礙物和已經在路中
if(a[next_x][next_y] == 0 and book[next_x][next_y] == 0):
book[next_x][next_y] = 1 #標記這個點已經被走過
dfs(next_x,next_y,end_x,end_y,migong_array,step+1)#嘗試下一個點
book[next_x][next_y] = 0#嘗試結束,取消這個點的標記
return