欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用python實現(xiàn)深度優(yōu)先遍歷搜索(DFS)的示例代碼

 更新時間:2024年01月15日 11:57:37   作者:程序員奇奇  
深度優(yōu)先搜索算法(Depth-First-Search,DFS)是一種用于遍歷或搜索樹或圖的算法,沿著樹的深度遍歷樹的節(jié)點,盡可能深的搜索樹的分支,本文給大家介紹了如何基于python實現(xiàn)深度優(yōu)先遍歷搜索(DFS),需要的朋友可以參考下

1.1 算法介紹

深度優(yōu)先搜索算法(Depth-First-Search,DFS)是一種用于遍歷或搜索樹或圖的算法。沿著樹的深度遍歷樹的節(jié)點,盡可能深的搜索樹的分支。當(dāng)節(jié)點 v 的所在邊都己被探尋過,搜索將回溯到發(fā)現(xiàn)節(jié)點 v 的那條邊的起始節(jié)點。這一過程一直進(jìn)行到已發(fā)現(xiàn)從源節(jié)點可達(dá)的所有節(jié)點為止。如果還存在未被發(fā)現(xiàn)的節(jié)點,則選擇其中一個作為源節(jié)點并重復(fù)以上過程,整個進(jìn)程反復(fù)進(jìn)行直到所有節(jié)點都被訪問為止。屬于盲目搜索。

以上圖為例,簡述 DFS 的過程。首先從根節(jié)點“1”出發(fā),按一定的順序遍歷其子節(jié)點,這里我們假設(shè)優(yōu)先遍歷左邊的。所以,在遍歷“1”之后,我們到了節(jié)點“2”,此時“2”仍有子節(jié)點,所以應(yīng)繼續(xù)向下遍歷,下一個節(jié)點是“3”,然后是“4”。到了“4”之后,沒有子節(jié)點了,說明我們已經(jīng)將這一條路遍歷完了,接著我們應(yīng)該回溯,應(yīng)該回到“4”的父節(jié)點,也就是“3”。因為“3”還有一個子節(jié)點“5”沒有遍歷,所以下一個我們應(yīng)該遍歷的是“5”。遍歷完“5”之后又發(fā)現(xiàn)一條路到頭了,再次回溯依然回溯到其父節(jié)點“3”,此時“3”的所有子節(jié)點都已經(jīng)遍歷完了,因該接著回溯到“3”的父節(jié)點“2”,然后檢查“2”是否有沒有遍歷完的子節(jié)點。按照這樣的規(guī)則,完成所有節(jié)點的遍歷。最終得到的遍歷順序是“1-2-3-4-5-6-7-8-9-10-11-12”

在介紹了 DFS 在遍歷樹的應(yīng)用后,我們將其應(yīng)用于八數(shù)碼問題的解決。八數(shù)碼問題也稱為九宮問題。在 3×3 的棋盤,擺有八個棋子,每個棋子上標(biāo)有 1 至 8 的某一數(shù)字,不同棋子上標(biāo)的數(shù)字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態(tài)和一個目標(biāo)狀態(tài),找出一種從初始轉(zhuǎn)變成目標(biāo)狀態(tài)的移動棋子步數(shù)最少的移動步驟。所謂問題的一個狀態(tài)就是棋子在棋盤上的一種擺法。棋子移動后,狀態(tài)就會發(fā)生改變。解八數(shù)碼問題實際上就是找出從初始狀態(tài)到達(dá)目標(biāo)狀態(tài)所經(jīng)過的一系列中間過渡狀態(tài)。

上面說的 DFS 遍歷的樹是已經(jīng)存在的,我們只需要按照規(guī)定的遍歷方法就能完成遍歷,而對于八數(shù)碼問題,沒有已經(jīng)存在的路徑供我們遍歷,需要我們從初始狀態(tài)向下延伸(也就是上下左右移動)才能構(gòu)造出類似的樹。

以上圖為例。在使用 DFS 進(jìn)行搜索時,每個狀態(tài)都會按照一定的順序進(jìn)行上下左右移動(在上圖中是下、左、右、上的順序),一次移動后會產(chǎn)生一個新的狀態(tài),然后以新狀態(tài)為起點繼續(xù)按約定的順序(例如先向下)移動。終止的條件是找到解或者達(dá)到深度界限。那么如果按照圖中下、左、右、上的順序搜索后的結(jié)果將會是最左邊的一條路一直是優(yōu)先向下移動,如果不能向下則依次會是左、右、上的一種。

1.2 實驗代碼

import copy
# 棋盤的類,實現(xiàn)移動和擴(kuò)展?fàn)顟B(tài)
class grid:
    def __init__(self, stat):
        self.pre = None
        self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
        self.stat = stat
        self.find0()
        self.update()
    #更新深度和距離和
    def update(self):
        self.fH()
        self.fG()
 
    # G是深度,也就是走的步數(shù)
    def fG(self):
        if (self.pre != None):
            self.G = self.pre.G + 1
        else:
            self.G = 0
 
    # H是和目標(biāo)狀態(tài)距離之和,可以用來判斷是否找到解
    def fH(self):
 
        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)
 
    #以三行三列的形式輸出當(dāng)前狀態(tài)
    def see(self):
        print("depth:", self.G)
        for i in range(3):
            print(self.stat[i])
        print("-" * 10)
 
    # 查看找到的解是如何從頭移動的
    def seeAns(self):
        ans = []
        ans.append(self)
        p = self.pre
        while (p):
            ans.append(p)
            p = p.pre
        ans.reverse()
        for i in ans:
            i.see()
    #找到數(shù)字x的位置
    def findx(self, x):
        for i in range(3):
            if (x in self.stat[i]):
                j = self.stat[i].index(x)
                return [i, j]
    #找到0的位置,也就是空白格的位置
    def find0(self):
        self.zero = self.findx(0)
 
    #對當(dāng)前狀態(tài)進(jìn)行擴(kuò)展,也就是上下左右移動,返回的列表中是狀態(tài)的二維列表,不是對象
    def expand(self):
        i = self.zero[0]
        j = self.zero[1]
        gridList = []
        if (j == 2 or j == 1):
            gridList.append(self.left())
        if (i == 2 or i == 1):
            gridList.append(self.up())
        if (i == 0 or i == 1):
            gridList.append(self.down())
        if (j == 0 or j == 1):
            gridList.append(self.right())
        return gridList
 
    # deepcopy多維列表的復(fù)制,防止指針賦值將原列表改變
    # move只能移動行或列,即row和col必有一個為0
    #對當(dāng)前狀態(tài)進(jìn)行移動的函數(shù)
    def move(self, row, col):
        newStat = copy.deepcopy(self.stat)
        tmp = self.stat[self.zero[0] + row][self.zero[1] + col]
        newStat[self.zero[0]][self.zero[1]] = tmp
        newStat[self.zero[0] + row][self.zero[1] + col] = 0
        return newStat
 
    def up(self):
        return self.move(-1, 0)
 
    def down(self):
        return self.move(1, 0)
 
    def left(self):
        return self.move(0, -1)
 
    def right(self):
        return self.move(0, 1)
 
# 判斷狀態(tài)g是否在狀態(tài)集合中,g是對象,gList是對象列表
# 返回的結(jié)果是一個列表,第一個值是真假,如果是真則第二個值是g在gList中的位置索引
def isin(g, gList):
    gstat = g.stat
    statList = []
    for i in gList:
        statList.append(i.stat)
    if (gstat in statList):
        res = [True, statList.index(gstat)]
    else:
        res = [False, 0]
    return res
 
# 計算逆序數(shù)之和
def N(nums):
    N=0
    for i in range(len(nums)):
        if(nums[i]!=0):
            for j in range(i):
                if(nums[j]>nums[i]):
                    N+=1
    return N
 
# 根據(jù)逆序數(shù)之和判斷所給八數(shù)碼是否可解
def judge(src,target):
    N1=N(src)
    N2=N(target)
    if(N1%2==N2%2):
        return True
    else:
        return False
 
# 初始狀態(tài)
startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
g = grid(startStat)
# 判斷所給的八數(shù)碼受否有解
if(judge(startStat,g.target)!=True):
    print("所給八數(shù)碼無解,請檢查輸入")
    exit(1)
# visited儲存的是已經(jīng)擴(kuò)展過的節(jié)點
visited = []
time = 0
# 用遞歸的方式進(jìn)行DFS遍歷
def DFSUtil(v, visited):
    global time
    #判斷是否達(dá)到深度界限
    if (v.G > 4):
        return
    time+=1
    #判斷是否已經(jīng)找到解
    if (v.H == 0):
        print("found and times", time, "moves:", v.G)
        v.seeAns()
        exit(1)
 
    #對當(dāng)前節(jié)點進(jìn)行擴(kuò)展
    visited.append(v.stat)
    expandStats = v.expand()
    w = []
    for stat in expandStats:
        tmpG = grid(stat)
        tmpG.pre = v
        tmpG.update()
        if (stat not in visited):
            w.append(tmpG)
    for vadj in w:
        DFSUtil(vadj, visited)
    #visited查重只對一條路,不是全局的,每條路開始時都為空
    #因為如果全局查重,會導(dǎo)致例如某條路在第100層找到的狀態(tài),在另一條路是第2層找到也會被當(dāng)做重復(fù)
    #進(jìn)而導(dǎo)致明明可能會找到解的路被放棄
    visited.pop()
 
DFSUtil(g, visited)
# 如果找到解程序會在中途退出,走到下面這一步證明沒有找到解
print("在當(dāng)前深度下沒有找到解,請嘗試增加搜索深度")

1.3 實驗結(jié)果

以下面這個八數(shù)碼為例,用 DFS 進(jìn)行搜索。

將找出的解從初始狀態(tài)一步一步輸出到解狀態(tài)。

可以看出總共進(jìn)行了 15 次遍歷,在某一條路的第 4 層找到了解。

下面我們來看一看 DFS 的所有 15 次遍歷,以此來更深入的理解 DFS 的原理。稍微對代碼進(jìn)行改動,使其輸出遍歷次數(shù)和當(dāng)前層數(shù)。由于結(jié)果太長,為了方便展示,下面將以樹的形式展示。

上面輸出的解就是按照紅色路線標(biāo)注找到的,從遍歷次數(shù)可以看出 DFS 是一條道走到黑的找法,因為設(shè)置的深度界限是 4,所以每一條路最多找到第 4 層。

1.4 實驗總結(jié)

1、為什么要設(shè)置深度界限?

因為理論上我們只需要一條路就可以找到解,只要不停地向下擴(kuò)展就可以了。而這樣做的缺點是會繞遠(yuǎn)路,也許第一條路找到第 100 層才找到解,但第二條路找兩層就能找到解。從 DFS 的原理出發(fā),我們不難看出這一點。還有一個問題是其狀態(tài)數(shù)太多了,在不設(shè)置深度界限的情況下經(jīng)常出現(xiàn)即使程序的棧滿了依然沒有找到解的情況。所以理論只是理論,在堅持“一條道走到黑”時,很可能因為程序“爆棧”而走到了黑還是沒有找到解。

2、如何進(jìn)行回溯?

在八數(shù)碼問題中,我們回溯的條件只有一個,就是達(dá)到深度界限了。因為在找到解時會退出,找不到時會繼續(xù)向下擴(kuò)展?;厮莸倪^程是先回溯到父節(jié)點,檢查父節(jié)點是否還能擴(kuò)展其他節(jié)點,如果能,就擴(kuò)展新的節(jié)點并繼續(xù)向下搜索,如果不能則遞歸地繼續(xù)向上回溯。

3、出現(xiàn)重復(fù)狀態(tài)怎么解決?

不難想出假如按照下、左、右、上這樣地順序進(jìn)行搜索時,在第三層時就會出現(xiàn)和初始狀態(tài)相同的情況。因為第二層向一個方向移動,第三層會有一個向反方向移動的狀態(tài)也就是回到初始狀態(tài)了。這樣不僅增加了運算量,而且沒有意義,會出現(xiàn)很多冗余步驟。所以我們應(yīng)該設(shè)置一個查重的表,將已經(jīng)遍歷過的狀態(tài)存入這個表中,當(dāng)再次遇到這種情況時我們就跳過。

那么這個查重的表是該對于全局而言呢,還是每條路的查重表是獨立的呢?在經(jīng)過很多測試之后,我發(fā)現(xiàn)這個查重表對每條路獨立是更好的。因為在一條路上出現(xiàn)的狀態(tài)所需要的步數(shù)和另一條需要的步數(shù)不一定相同,也就是說我在第一條路上的第 100 層找到了某個狀態(tài),放入了查重表中,但是這個狀態(tài)可能在另一條路上第 2 層就能找到,或許再下面幾層就能找到解了,可是由于被放進(jìn)了全局查重表中而放棄了這個條路的擴(kuò)展,也損失了更快找到解的機(jī)會。所以一條路一個查重表是好的。

4、由于需要設(shè)置深度界限,每條路都會在深度界限處截至, 而如果所給的八數(shù)碼的最優(yōu)解大于深度界限,就會出現(xiàn)遍歷完所有情況都找不解。而在事先不知道最優(yōu)解的深度的情況下這個深度界限很難確定,設(shè)置大了會增大搜索時間,設(shè)置小了會找不到解。這也是 DFS 的一個缺點。

5、DFS 不一定能找到最優(yōu)解。因為深度界限的原因,找到的解可能在最優(yōu)解和深度界限之間。

以上就是使用python實現(xiàn)深度優(yōu)先遍歷搜索(DFS)的詳細(xì)內(nèi)容,更多關(guān)于python深度優(yōu)先遍歷搜索的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python Opencv任意形狀目標(biāo)檢測并繪制框圖

    Python Opencv任意形狀目標(biāo)檢測并繪制框圖

    這篇文章主要為大家詳細(xì)介紹了Python Opencv任意形狀目標(biāo)檢測,并繪制框圖,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Python3.5面向?qū)ο笈c繼承圖文實例詳解

    Python3.5面向?qū)ο笈c繼承圖文實例詳解

    這篇文章主要介紹了Python3.5面向?qū)ο笈c繼承,結(jié)合圖文與實例形式詳細(xì)分析了Python3.5面向?qū)ο笈c繼承的相關(guān)概念、原理、實現(xiàn)方法及操作注意事項,需要的朋友可以參考下
    2019-04-04
  • Python同步遍歷多個列表的示例

    Python同步遍歷多個列表的示例

    今天小編就為大家分享一篇Python同步遍歷多個列表的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-02-02
  • pytorch 預(yù)訓(xùn)練模型讀取修改相關(guān)參數(shù)的填坑問題

    pytorch 預(yù)訓(xùn)練模型讀取修改相關(guān)參數(shù)的填坑問題

    這篇文章主要介紹了pytorch 預(yù)訓(xùn)練模型讀取修改相關(guān)參數(shù)的填坑問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • QML使用Python的函數(shù)過程解析

    QML使用Python的函數(shù)過程解析

    這篇文章主要介紹了QML使用Python的函數(shù)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • python常用的魔法方法(雙下劃線)

    python常用的魔法方法(雙下劃線)

    本文介紹一下python中常用的魔法方法以及面向?qū)ο笾蟹浅V匾膯卫J健>哂幸欢ǖ膮⒖純r值,感興趣的可以了解一下
    2021-09-09
  • Python Pandas學(xué)習(xí)之Pandas數(shù)據(jù)結(jié)構(gòu)詳解

    Python Pandas學(xué)習(xí)之Pandas數(shù)據(jù)結(jié)構(gòu)詳解

    Pandas中一共有三種數(shù)據(jù)結(jié)構(gòu),分別為:Series、DataFrame和MultiIndex(老版本中叫Panel )。其中Series是一維數(shù)據(jù)結(jié)構(gòu),DataFrame是二維的表格型數(shù)據(jù)結(jié)構(gòu),MultiIndex是三維的數(shù)據(jù)結(jié)構(gòu)。本文將詳細(xì)為大家講解這三個數(shù)據(jù)結(jié)構(gòu),需要的可以參考一下
    2022-02-02
  • Python刪除字典中的某個key的常用方法

    Python刪除字典中的某個key的常用方法

    字典是Python中的一種數(shù)據(jù)類型,它是一個無序的鍵值對集合,在實際的編程中,我們經(jīng)常需要刪除字典中的某個鍵值對,本文將從多個角度分析Python刪除字典中的某個key的方法,需要的朋友可以參考下
    2024-10-10
  • pyd文件逆向的方法實現(xiàn)

    pyd文件逆向的方法實現(xiàn)

    pyd文件是由非 Python,其它編程語言編寫編譯生成的 Python 擴(kuò)展模塊,本文主要介紹了pyd文件逆向的方法實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Python字符串格式化%s%d%f詳解

    Python字符串格式化%s%d%f詳解

    這篇文章主要介紹了Python字符串格式化%s%d%f詳解,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02

最新評論