深度優先搜索和廣度優先搜索

不撞南牆不回頭-深度優先搜索

基礎部分

對於深度優先搜索和廣度優先搜索,我很難形象的去表達它的定義。咱們從一個例子來切入。node

輸入一個數字n,輸出1~n的全排列。即n=3時,輸出123,132,213,231,312,321python

把問題形象化,假若有1,2,3三張撲克牌和編號爲1,2,3的三個箱子,把三張撲克牌分別放到三個箱子裏有幾種方法?算法

咱們用深度優先遍歷搜索的思想來考慮這個問題。數組

到1號箱子面前時,咱們手裏有1,2,3三種牌,咱們把1放進去,而後走到2號箱子面籤,手裏有2,3兩張牌, 而後咱們把2放進去,再走到3號箱子前,手裏以後3這張牌,因此把3放進去,而後再往前走到咱們想象出來的一個4號箱子前,咱們手裏沒牌了,因此,前面三個箱子中放牌的組合就是要輸出的一種組合方式。(123)app

而後咱們後退到3號箱子,把3這張拍取出來,由於這時咱們手裏只有一張牌,因此再往裏放的話仍是原來那種狀況,因此咱們還要再日後推,推到2號箱子前,把2從箱子中取出來,這時候咱們手裏有2,3兩張牌,這時咱們能夠把3放進2號箱子,而後走到3號箱子中把2放進去,這又是一種要輸出的組合方式.(132)框架

就找這個思路繼續下去再次回退的時候,咱們就要退到1號箱,取出1,而後分別放2和3進去,而後產生其他的組合方式。spa

有點囉嗦,可是基本是這麼一個思路。debug

咱們來看一下實現的代碼指針

def sortNumber(self, n):
        flag = [False for i in range(n)]
        a = [0 for i in range(n)]
        l = []
        
        def dfs(step):
            if step == n:
                l.append(a[:])
                return
            for i in range(n):
                if flag[i] is False:
                    flag[i] = True
                    a[step] = i
                    dfs(step + 1)
                    flag[i] = False
        dfs(0)
        return l

輸出是code

[[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]

咱們建立的a這個list至關於上面說到的箱子,flag這個list呢,來標識某一個數字是否已經被用過了。

其實主要的思想就這dfs方法裏面的這個for循環中,在依次的排序中,咱們默認優先使用最小的那個數字,這個for循環其實就表明了一個位置上有機會放全部的這些數字,這個flag標識就避免了在一個位置重複使用數字的問題。

若是if 成立,說明當前位置可使用這個數字,因此把這個數字放到a這個數組中,而後flag相同爲的標識改成True,也就是說明這個數已經被佔用了,而後在調用方法自己,進行下一步。

flag[i] = False這句代碼是很重要的,在上面的dfs(也就是下一步)結束以後,返回到當前這個階段,咱們必須模擬收回這個數字,也就是把flag置位False,表示這個數字又能夠用了。

思路大概就是這樣子的,這就是深度優先搜索的一個簡單的場景。用debug跟一下,一步一步的來看代碼就更清晰的了。

迷宮問題

上面咱們已經簡單的瞭解了深度優先搜索,下面咱們經過一個迷宮的問題來進一步數字這個算法,而後同時引出咱們的廣度優先搜索。

迷宮是由m行n列的單元格組成,每一個單元格要不是空地,要不就是障礙物,咱們的任務是找到一條從起點到終點的最短路徑。

咱們抽象成模型來看一下

模型

start表明起點,end表明終點,x表明障礙物也就是不能經過的點。

首先咱們來分析一下,從start(0,0)這個點,甚至說是每個點出發,都有四個方向能夠走,上下左右,僅對於(0,0)這個點來講,只能往右和下走,由於往左和上就到了單元格外面了,咱們能夠稱之爲越界了。

咱們用深度優先的思想來考慮的話,咱們能夠從出發點開始,所有都先往一個方向走,而後走到遇到障礙物或者到了邊界的狀況下,在改變另外一個方向,而後再走到底,這樣一直走下去。

拿到咱們這個題目中,咱們能夠這樣來思考,在走的時候,咱們規定一個右下左上這樣的順序,也就是先往右走,走到不能往右走的時候在變換方向。好比咱們從(0,0)走到(0,1)這個點,在(0,1)這個點也是先往右走,可是咱們發現(0,2)是障礙物,因此咱們就改變爲往下走,走到(1,1),而後在(1,1)開始也是先向右走,這樣一直走下去,直到找到咱們的目標點。

其中咱們要注意一點,在右下左上這四個方向中有一個方向是咱們來時候的方向,在當前這個點,四個方向沒有走完以前咱們不要後退到上一個點,因此咱們也須要一個像前面排數字代碼裏面的flag數組來記錄當前位置時候被佔用。咱們必須是四個方向都走完了才能日後退到上一個換方向。

下面我貼一下代碼

def depthFirstSearch(self):
        m = 5
        n = 4

        # 5行 4 列
        flag = [[False for i in range(n)] for j in range(m)]
        # 存儲不能同行的位置
        a = [[False for i in range(n)] for j in range(m)]
        a[0][2] = True
        a[2][2] = True
        a[3][1] = True
        a[4][3] = True

        global min_step
        min_step = 99999

        director_l = [[0, 1], [1, 0], [0, -1], [-1, 0]]

        def dfs(x, y, step):

            # 什麼狀況下中止 (找到目標座標)
            if x == 3 and y == 2:
                global min_step
                if step < min_step:
                    min_step = step
                return

            # 右下左上
            for i in range(4):
                # 下一個點
                nextX = x + director_l[i][0]
                nextY = y + director_l[i][1]

                # 是否越界
                if nextX < 0 or nextX >= m or nextY < 0 or nextY >= n:
                    continue

                # 不是障礙  and 改點尚未走過
                if a[x][y] is False and flag[x][y] is False:
                    flag[x][y] = True
                    dfs(nextX, nextY, step+1)
                    flag[x][y] = False #回收

        dfs(0, 0, 0)
        return min_step

首先flag這個算是二位數組吧,來記錄咱們位置是否佔用了,而後a這個數組,是來記錄整個單元格的,也就是標識那些障礙物的位置座標。一樣的,重點是這個dfs方法,他的參數x,y是指當前的座標,step是步數。

這個你們能夠看到一個director_l的數組,他是來輔助咱們根據當前左邊和不一樣方向計算下一個位置的座標的。

dfs中咱們已經註明了搜索中止的判斷方式,也就是找到(3,2)這個點,而後下面的for循環,則表明四個不一樣的方向,每個方向咱們都會先求出他的位置,而後判斷是否越界,若是沒有越界在判斷是不是障礙或者是否已經走過了,知足了全部的判斷條件,咱們在繼續往下一個點,直到找到目標,比較路徑的步數。

這就是深度優先搜索了,固然,這個題目咱們還有別的解法,這就到了咱們說的廣度優先搜索。

層層遞進-廣度優先搜索

咱們先大致說一下廣度優先搜索的思路,深度優先是先窮盡一個方向,而廣度優先呢,則是基於一個位置,先拿到他全部能到達的位置,而後分別基於這些新位置,拿到他們能到達的全部位置,一次這樣層層的遞進,直到找到咱們的終點。

示例

從(0,0)出發,能夠到達(0,1)和(1,0),而後再從(0,1)出發到達(1,1),從(1,0)出發,到達(2,0)和(1,1),以此類推。

因此咱們咱們維護一個隊列來儲存每一層遍歷到達的點,固然了,不要重複儲存同一個點。咱們用一個指針head來標識當前的基準位置,也就是說最開始指向(0,0),當儲存完畢全部(0,0)能抵達的位置時,咱們就應該改變咱們的基準位置了,這時候head++,就到了(0,1)這個位置,而後儲存完他能到的全部位置,head++,就到了(1,0),而後繼續。

def breadthFirstSearch(self):

        class Node:
            def __init__(self):
                x = 0
                y = 0
                step = 0

        m, n = 5, 4
        # 記錄
        flag = [[False for i in range(n)] for j in range(m)]

        # 儲存地圖信息
        a = [[False for i in range(n)] for j in range(m)]
        a[0][2] = True
        a[2][2] = True
        a[3][1] = True
        a[4][3] = True
        # 隊列
        l = []
        startX, startY, step = 0, 0, 0
        head = 0
        index = 0

        node = Node()
        node.x = startX
        node.y = startY
        node.step = step
        index += 1
        l.append(node)
        flag[0][0] = True

        director_l = [[0, 1], [1, 0], [0, -1], [-1, 0]]

        while head < index:

            last_node = l[head]
            # 處理四個方向
            for i in range(4):

                # 當前位置
                currentX = last_node.x + director_l[i][0]
                currentY = last_node.y + director_l[i][1]

                # 找到目標
                if currentX == 4 and currentY == 2:
                    print('step = ' + str(last_node.step + 1))
                    return

                #是否越界
                if currentX < 0 or currentY < 0 or currentX >= m or currentY >= n:
                    continue

                if a[currentX][currentY] is False and flag[currentX][currentY] is False:


                    #不是目標
                    flag[currentX][currentY] = True

                    node_new = Node()
                    node_new.x = currentX
                    node_new.y = currentY
                    node_new.step = last_node.step+1
                    l.append(node_new)
                    index += 1



            head += 1

首先咱們定義了一個節點Node的類,來封裝節點位置和當前的步數,flag,a,director_l這兩個數組做用跟深度優先搜索相同,l是咱們維護的隊列,head指針指向當前基準的那個位置的,index指針指向隊列尾。首先咱們先把第一個Node(也就是起點)存進隊列,廣度優先搜索不須要遞歸,只要加一個循環就行。

每次走到符合要求的位置,咱們便把他封裝成Node來存進對列中,每存一個index都要+1.

head指針必須在一個節點四個方向都處理完了以後才能夠+1,變換下一個基準節點。

小結

簡單的介紹了深度優先搜索和廣度優先搜索,深度優先有一種先窮盡一個方向而後結合使用回溯來找到解,廣度呢,可能就是每作一次操做就涵蓋了全部的可能結果,而後一步步日後推出去,找到最後的解。這算我我的的理解吧,不許確也不官方,思想也只能算是稍有體會,還得繼續努力。

題外話

礙於本身的算法基礎太差,最近一直在作算法題,我是先刷了一段時間的題目,發現吃力了,纔開始看的書。感受有點本末倒置。其實應該是先看看書,把算法的一些經常使用大類搞清楚了,造成一個知識框架,這樣在遇到問題的時候能夠知道往那些方向上面思考,可能會好一些吧。

相關文章
相關標籤/搜索