[搜索算法系列] —— 深度優先搜索

搜索本質上也是對解空間的枚舉,本文介紹搜索算法中的深度優先搜索(圖論)。python

全排列問題

給定一個 沒有重複數字的序列,返回其全部可能的全排列。例如對於數列[1, 2, 3]其全排列爲[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]。

咱們可使用n層循環,每一層循環內肯定一位數字,在最內層循環內判斷該排列是否符合要求,例如對於數列nums = [1, 2, 3],能夠寫出以下代碼。算法

for i in nums:
    for j in nums:
        for k in nums:
            if i != j and j != k and i != k:
                print i, j, k

這道題目分析到這裏,其實和個人第一篇文章的問題頗有很大類似的地方,但不一樣的是在於百雞百錢問題的自變量個數是固定的,即循環層數是固定的。數組

上述代碼中咱們試圖經過每一層循環來肯定一個數值,但這段代碼只適用於len(nums) == 3的狀況,可是若是nums長度爲4,5,或更高呢?咱們沒法動態生成n層循環,除非是用程序編寫程序,遞歸爲咱們巧妙地解決了這個問題。bash

在遞歸中,咱們則經過每一層函數的嵌套來肯定一個數值,而且咱們只需給出頂層的實現就夠了。app

因而咱們得出了下面的代碼(涉及python中list與set的使用)。函數

def solution(nums, status):
    if set(nums) == set(status):
        print status
    for x in nums:
        solution(nums, status+[x])

上述代碼中,status表示當前函數層次的狀態,即一個排列結果,該遞歸函數能夠理解爲一個數學表達式:solution = for + solution,那麼該solution函數就會被展開爲下面的樣子。post

for ... in range(...):
    for ... in range(...):
        ...
        if ... : print ... # 只有在第n層,條件纔會成立

這就和咱們最開始給出的代碼看上去差很少了。可是如今代碼雖然是有限的,可實際展開的時候依舊是無窮無盡的,這就須要咱們爲solution函數加上一個終止條件,也稱遞歸出口spa

若是把遞歸的過程想象成包子餡的包子,那麼若是沒有遞歸出口,這個包子將會變成饅頭。code

那麼遞歸出口咱們如何去定義呢?blog

經過題意不難推出當對於當前層,若當前排列結果status長度大於nums數列長度,便可終止遞歸。

因而可得出正確代碼以下。

def solution(nums, status):
    if len(status) > len(nums):
        return
    if set(nums) == set(status):
        print status
    for x in nums:
        solution(nums, status+[x])

solution([1,2,3], [])
# [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

這段代碼雖然能夠正確運行,但咱們可讓他更加美觀。最終代碼以下。

這一次,咱們將遞歸出口定義在進入遞歸函數前,而且將中間狀態記錄在了ans數組中。

def solution(nums, status, ans):
    if len(nums) == len(status):
        ans.append(status)
    for x in nums:
        if x not in status:
            solution(nums, status+[x], ans)
    return ans

print solution([1,2,3], [], [])
# [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

若是在solution的開始輸出status的值,咱們會獲得以下的輸出結果。

[1]
    [1, 2]
        [1, 2, 3]
    [1, 3]
        [1, 3, 2]
[2]
    [2, 1]
        [2, 1, 3]
    [2, 3]
        [2, 3, 1]
[3]
    [3, 1]
        [3, 1, 2]
    [3, 2]
        [3, 2, 1]

觀察程序的輸出與下面的圖片,體會該迭代方法被稱做深度優先搜索的緣由。

本文示例題目與leecode 46.全排列一致,讀者可自行嘗試提交,驗證本身代碼的正確性。

相關文章
相關標籤/搜索