用Python實(shí)現(xiàn)樹的BFS與DFS

一乌奇、BFS與DFS簡介

理解動(dòng)態(tài)規(guī)劃没讲、BFS和DFS一文中,已經(jīng)集合具體例子华弓,介紹了圖的BFS與DFS食零。但是比較復(fù)雜,這里就寫的簡單點(diǎn)吧寂屏。

BFS:每次輸出一行贰谣,所用數(shù)據(jù)結(jié)構(gòu)為隊(duì)列
設(shè)想我們每次都從左到右、從上到下的去遍歷一個(gè)圖迁霎,那么就需要把一行中最左邊先進(jìn)來的先輸出吱抚,最右邊后進(jìn)來的后輸出。所以會(huì)用到隊(duì)列考廉。

DFS:每次深挖到底秘豹,所用數(shù)據(jù)結(jié)構(gòu)為棧
設(shè)想我們從圖的最上邊先按照一條道深挖到最下面,在挖到底以后就需要再逐個(gè)返回到上面的頂點(diǎn)昌粤,再去遍歷父節(jié)點(diǎn)是不是還有別的子節(jié)點(diǎn)既绕。后進(jìn)先出的模式,所以需要用到棧涮坐。

因?yàn)檫@是在圖中凄贩,所以,一個(gè)頂點(diǎn)的相鄰點(diǎn)可能包含著已經(jīng)搜索過的點(diǎn)袱讹,因此這兩個(gè)遍歷都需要加一個(gè)集合nodeSet疲扎,里面是已經(jīng)遍歷過的點(diǎn)。在搜索過程中捷雕,如果發(fā)現(xiàn)新的節(jié)點(diǎn)已經(jīng)存在于nodeSet中椒丧,那么就不對新節(jié)點(diǎn)進(jìn)行搜索了。

而救巷,如果是在樹中用BFS與DFS壶熏,因?yàn)橐粋€(gè)節(jié)點(diǎn)頂多有兩個(gè)子節(jié)點(diǎn),我們已經(jīng)明確知道這個(gè)節(jié)點(diǎn)除了子節(jié)點(diǎn)以外不會(huì)再有相鄰節(jié)點(diǎn)征绸,因此在搜索過程中也不會(huì)遇到重復(fù)的節(jié)點(diǎn)久橙,所以不需要加nodeSet。只需要按照BFS與DFS的思想與所用數(shù)據(jù)結(jié)構(gòu)管怠,遍歷即可淆衷。

二、代碼實(shí)現(xiàn)

參考 圖的廣度優(yōu)先搜索(BFS)與深度優(yōu)先搜索(DFS) Python實(shí)現(xiàn)

2.1渤弛、樹的廣度優(yōu)先搜索

因?yàn)槭菢渥U總€(gè)node至多有兩個(gè)子節(jié)點(diǎn),而下面代碼中語句是對圖中不確定的子節(jié)點(diǎn)個(gè)數(shù)來說的,因此下面的這句

for next in cur.nexts:

都可以用對左右兩個(gè)子節(jié)點(diǎn)的遍歷來代替佳头,參考前序遍歷那一篇文章鹰贵。
不過為了體現(xiàn)圖的思想,這里都用上面這句代碼來實(shí)現(xiàn)吧康嘉。

  • 1.利用隊(duì)列實(shí)現(xiàn)
  • 2.從源節(jié)點(diǎn)開始依次按照寬度進(jìn)隊(duì)列碉输,然后彈出
  • 3.每彈出一個(gè)節(jié)點(diǎn),就把該節(jié)點(diǎn)所有沒有進(jìn)過隊(duì)列的鄰接點(diǎn)放入隊(duì)列
  • 4.直到隊(duì)列變空

這里如果需要按照BFS的順序輸出二叉樹的元素的話亭珍,那么就可以把nodeSet去掉了敷钾。

def bfs(node):
    if node is None:
        return
    queue = []
    #nodeSet = set() 
    queue.insert(0,node)
    #nodeSet.add(node)
    while queue:
        cur = queue.pop()               # 彈出元素
        print(cur.val)                # 打印元素值
        for next in cur.nexts:          # 遍歷元素的鄰接節(jié)點(diǎn)
            #if next not in nodeSet:     # 若鄰接節(jié)點(diǎn)沒有入過隊(duì),加入隊(duì)列并登記
                #nodeSet.add(next)
                queue.insert(0,next)

2.2肄梨、樹的深度優(yōu)先搜索

2.2.1阻荒、用遞歸的方法進(jìn)行dfs

這里遞歸不需要nodeSet

因?yàn)橄氲接袟5乃枷耄敲淳筒豢杀苊獾南氲竭f歸众羡。

  • 遞歸結(jié)束條件:節(jié)點(diǎn)是None侨赡,結(jié)束函數(shù)調(diào)用
  • 遞歸改變:每次都要把節(jié)點(diǎn)添加到節(jié)點(diǎn)集合當(dāng)中去
  • 遞歸調(diào)用:對于每一個(gè)當(dāng)前節(jié)點(diǎn)的相鄰節(jié)點(diǎn),只要不在節(jié)點(diǎn)集合中粱侣,就調(diào)用dfs進(jìn)行搜索
#nodeSet = set()
def dfs1(node):
    if node is None:
        return
    #nodeSet.add(node)
    print(node.val)
    #相當(dāng)于樹的前序遍歷了羊壹,只不過這里把左右子節(jié)點(diǎn)放到了nexts的列表中
    for next in node.nexts:
        #if next not in nodeSet:
            dfs1(next)

2.2.2、用循環(huán)的方法進(jìn)行dfs

這個(gè)方法其實(shí)是圖的遍歷方法齐婴,對于樹的DFS舶掖,其實(shí)不需要用nodeSet,可以參考樹3尔店,用循環(huán)實(shí)現(xiàn)樹的三種遍歷

若是按照如下方法進(jìn)行dfs,那么還是要用nodeSet來保證不會(huì)重復(fù)遍歷一些節(jié)點(diǎn)了主慰。

用循環(huán)的方法嚣州,就一定會(huì)用到棧了。
對于下面代碼共螺,有兩個(gè)地方需要解釋:
1该肴、為什么彈出了cur以后還要把cur壓入棧中?

答:為了保證出入棧的順序藐不,若是cur沒有子節(jié)點(diǎn)匀哄,則直接彈出棧。如果有子節(jié)點(diǎn)雏蛮,那么就要再把cur壓入棧涎嚼,再把cur的子節(jié)點(diǎn)壓入棧。這樣挑秉,下一次棧彈出的會(huì)是cur的子節(jié)點(diǎn)法梯,在子節(jié)點(diǎn)遍歷完并且再次彈出棧以后,當(dāng)前節(jié)點(diǎn)的索引又會(huì)回到父節(jié)點(diǎn)cur上。

2立哑、為什么最后要用break語句夜惭?

**保持深度優(yōu)先的順序,在遍歷完cur的左子節(jié)點(diǎn)以后铛绰,下一步會(huì)跳出循環(huán)诈茧,

  • 你可以看看,若是不用break會(huì)是什么效果:以樹為例捂掰,在搜索完根節(jié)點(diǎn)的左子節(jié)點(diǎn)以后敢会,緊接著會(huì)把右子節(jié)點(diǎn)壓入棧中。而不是我們預(yù)想的把左子節(jié)點(diǎn)的左子節(jié)點(diǎn)壓入棧中尘颓。
  • 在對root搜索到左子節(jié)點(diǎn)L1以后 走触,此時(shí)我們想對L1進(jìn)行搜索,而我們又想到此時(shí)此時(shí)此時(shí)疤苹,L1是在棧頂?shù)幕ス悖俏覀兒尾恢苯觔reak出循環(huán),直接到對L1進(jìn)行搜索卧土。
    所以循環(huán)中用了一個(gè)break來保持深度優(yōu)先
def dfs(node):
    if node is None:
        return
    nodeSet = set()
    stack = []
    print(node.val)
    nodeSet.add(node)
    stack.append(node)
    while len(stack) > 0:
        cur = stack.pop()               # 彈出最近入棧的節(jié)點(diǎn)
        for next in cur.nexts:         # 遍歷該節(jié)點(diǎn)的鄰接節(jié)點(diǎn)
            if next not in nodeSet:    # 如果鄰接節(jié)點(diǎn)不重復(fù)
                stack.append(cur)       # 把節(jié)點(diǎn)壓入
                stack.append(next)      # 把鄰接節(jié)點(diǎn)壓入
                nodeSet.add(next)           # 登記節(jié)點(diǎn)
                print(next.val)       # 打印節(jié)點(diǎn)值
                break                   # 退出惫皱,保持深度優(yōu)先

用二叉樹來檢驗(yàn)上述算法

對于TreeNode類,在構(gòu)造函數(shù)中又添加了一個(gè)next屬性近似代替圖中的臨近節(jié)點(diǎn)列表尤莺。

#實(shí)現(xiàn)一個(gè)二叉樹旅敷,并且用BFS或DFS去遍歷她
class TreeNode:
    def __init__(self,x):
        self.val = x
        self.left = None
        self.right = None
        self.nexts = []

        
root_node = TreeNode(1)
node_2 = TreeNode(2)
node_3 = TreeNode(3)
node_4 = TreeNode(4)
node_5 = TreeNode(5)
node_6 = TreeNode(6)
node_7 = TreeNode(7)
node_8 = TreeNode(8)
node_9 = TreeNode(9)
node_10 = TreeNode(10)

def littleTree(root,left,right):
    root.left = left
    root.right = right
    if root.left:
        root.nexts.append(root.left)
    if root.right:
        root.nexts.append(root.right)

littleTree(root_node, node_2, node_3)
littleTree(node_2, node_4, node_5)
littleTree(node_3, node_6, node_7)
littleTree(node_4, node_8, node_9)
littleTree(node_5, node_10, None)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市颤霎,隨后出現(xiàn)的幾起案子媳谁,更是在濱河造成了極大的恐慌,老刑警劉巖友酱,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晴音,死亡現(xiàn)場離奇詭異,居然都是意外死亡缔杉,警方通過查閱死者的電腦和手機(jī)锤躁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來或详,“玉大人系羞,你說我怎么就攤上這事“郧伲” “怎么了椒振?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沈贝。 經(jīng)常有香客問我杠人,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任嗡善,我火速辦了婚禮辑莫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罩引。我一直安慰自己各吨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布袁铐。 她就那樣靜靜地躺著揭蜒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剔桨。 梳的紋絲不亂的頭發(fā)上屉更,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音洒缀,去河邊找鬼瑰谜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛树绩,可吹牛的內(nèi)容都是我干的萨脑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼饺饭,長吁一口氣:“原來是場噩夢啊……” “哼渤早!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘫俊,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鹊杖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扛芽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仅淑,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年胸哥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赡鲜。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡空厌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出银酬,到底是詐尸還是另有隱情嘲更,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布揩瞪,位于F島的核電站赋朦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宠哄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一壹将、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毛嫉,春花似錦诽俯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辛臊,卻和暖如春仙粱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彻舰。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工伐割, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淹遵。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓口猜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親透揣。 傳聞我的和親對象是個(gè)殘疾皇子济炎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容