日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

算法与数据结构--空间复杂度O(1)遍历树

發(fā)布時(shí)間:2024/7/5 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法与数据结构--空间复杂度O(1)遍历树 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

大家好~我叫「小鹿鹿鹿」,是本賣(mài)萌小屋的第二位簽約作(萌)者(貨)。和小夕一樣現(xiàn)在在從事NLP相關(guān)工作,希望和大家分享NLP相關(guān)的、不限于NLP的各種小想法,新技術(shù)。這是我的第一篇試水文章,初來(lái)乍到,希望大噶多多滋辭(●'?'●)。

冬天已經(jīng)來(lái)了,秋招早已悄無(wú)聲息的結(jié)束。作為一個(gè)已經(jīng)工作了兩年的老人,校招面試感覺(jué)就像高考一樣遙遠(yuǎn)。但是呢,雖然工作了,還是要時(shí)刻保持危機(jī)感的呀,萬(wàn)一哪天就要跳槽了呢( ̄? ̄)。

遙想當(dāng)年面試的時(shí)候,由于沒(méi)有學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu),在面試官出算法題之前就老實(shí)交待家底:“我的算法和數(shù)據(jù)結(jié)構(gòu)不太行,樹(shù)呀圖呀都不太會(huì)(????)"。但是經(jīng)過(guò)兩年斷斷續(xù)續(xù)的學(xué)習(xí),發(fā)現(xiàn)其實(shí)樹(shù)是一個(gè)套路非常明顯的一類(lèi)算法題,而遍歷樹(shù)是解決絕大多數(shù)樹(shù)問(wèn)題的基礎(chǔ)(很多題目都是在樹(shù)的遍歷上擴(kuò)展),下面小鹿就以樹(shù)的遍歷為例,解剖樹(shù)里面深深的套路吧o(* ̄▽ ̄*)o。

?

樹(shù)的基礎(chǔ)回顧


二叉樹(shù)長(zhǎng)什么樣子,小鹿這里就不上圖啦,所謂的根節(jié)點(diǎn)、葉子節(jié)點(diǎn)也不介紹啦。我們知道,二叉樹(shù)的遍歷分為三種:前序、中序和后序。這三種序的不同主要就是在于什么時(shí)候訪問(wèn)根節(jié)點(diǎn)。以前序?yàn)槔?#xff0c;在遍歷一顆樹(shù)的時(shí)候先訪問(wèn)根節(jié)點(diǎn),再遍歷其根節(jié)點(diǎn)的左子樹(shù),最后訪問(wèn)根節(jié)點(diǎn)的右子樹(shù)。而中序遍歷就是先遍歷左子樹(shù),再訪問(wèn)根節(jié)點(diǎn),最后遍歷右子樹(shù);后序遍歷小鹿就不重復(fù)啦。

樹(shù)的遞歸


樹(shù)有一個(gè)很好的特性,就是一棵樹(shù)的根節(jié)點(diǎn)的左右子樹(shù)仍然是一顆樹(shù)

這好像是一句正確的廢話(huà)╮( ̄▽ ̄"")╭

所以我們可以把一棵復(fù)雜的大樹(shù),分解成兩顆稍微小一點(diǎn)的樹(shù),依次類(lèi)推,最后變成最小的單元(只有一個(gè)節(jié)點(diǎn)的樹(shù))。不管怎么講,處理只有一個(gè)節(jié)點(diǎn)的樹(shù)是不是超級(jí)容易!這個(gè)呢就是遞歸的思想,所以說(shuō)到這里,我們以后只要遇到關(guān)于樹(shù)的問(wèn)題,誰(shuí)還不會(huì)用遞歸走一波呢!

下面就來(lái)開(kāi)始實(shí)踐!用遞歸的思想實(shí)現(xiàn)二叉樹(shù)的前序、中序、后序遍歷(遍歷結(jié)果記錄在self.ans的向量里)。小鹿這里就用python寫(xiě)啦(真的不要糾結(jié)編程語(yǔ)言噢)。遍歷一個(gè)有n個(gè)節(jié)點(diǎn)的樹(shù),其時(shí)間復(fù)雜度和空間復(fù)雜度都是O(n)。

class Solution(object):def __init__(self):self.ans = []def preorderRecursive(self, root):if root:self.ans.append(root.val)self.preorderRecursive(root.left)self.perorderRecursive(root.right)def inorderRecursive(self, root):if root:self.inorderRecursive(root.left)self.ans.append(root.val)self.inorderRecursive(root.right)def postorderRecursive(self, root):if root:self.postorderRecursive(root.left)self.postorderRecursive(root.right)self.ans.append(root.val)

樹(shù)和棧


用遞歸的思路解決樹(shù)的遍歷或者其他樹(shù)的問(wèn)題,思路非常清晰,代碼也會(huì)非常的簡(jiǎn)單清楚。當(dāng)我們?cè)谑褂眠f歸(函數(shù)的嵌套)的時(shí)候,其本質(zhì)是在調(diào)用棧。我們可以用棧來(lái)實(shí)現(xiàn)樹(shù)的遍歷,進(jìn)一步理解遞歸的思想。

class Solution(object):def __init__(self):self.ans = []def preorderStack(self, root):aStack = []if root:aStack.append(root)while aStack:p = aStack.pop()self.ans.append(p.val)if p.right:aStack.append(p.right)if p.left:aStack.append(p.left)return self.ansdef inorderStack(self, root):stack = []p = rootwhile p:stack.append(p)p = p.leftwhile stack:p = stack.pop()self.ans.append(p.val)p = p.rightwhile p:stack.append(p)p = p.leftreturn self.ansdef postorderStack(self, root)aStack = []prev = Nonep = rootwhile aStack or p:if p:aStack.append(p)p = p.leftelse:p = aStack[-1]if p.right == prev or p.right is None:self.ans.append(p.val)prev = paStack.pop()p = Noneelse:p = p.right return self.ans

用棧來(lái)實(shí)現(xiàn)樹(shù)的遍歷就稍微復(fù)雜一點(diǎn)啦。首先前序是最簡(jiǎn)單的,我們用一個(gè)棧(first-in-last-out)來(lái)維護(hù)樹(shù)遍歷的順序。初始狀態(tài)是把非空的根節(jié)點(diǎn)push到棧中,逐個(gè)從棧中取出節(jié)點(diǎn),訪問(wèn)其值,然后先push右節(jié)點(diǎn)再push左節(jié)點(diǎn)到棧中,就保證訪問(wèn)順序是?root->left->right?啦。超級(jí)簡(jiǎn)單有木有!

中序遍歷就稍微難一些了,在訪問(wèn)一個(gè)節(jié)點(diǎn)之前需要訪問(wèn)其 left-most?節(jié)點(diǎn),將其左節(jié)點(diǎn)逐個(gè)加入到棧中,直到當(dāng)前節(jié)點(diǎn)為None。然后從棧中取出節(jié)點(diǎn),由于棧的特性,在取出某個(gè)節(jié)點(diǎn)之前,其左節(jié)點(diǎn)已經(jīng)被訪問(wèn),所以就可以安心的訪問(wèn)該節(jié)點(diǎn),然后把當(dāng)前節(jié)點(diǎn)置為其右節(jié)點(diǎn),依次類(lèi)推。

后序遍歷和中序遍歷差不多,在中序遍歷的基礎(chǔ)上需要增加一個(gè)prev記錄上一個(gè)訪問(wèn)的節(jié)點(diǎn),確認(rèn)在訪問(wèn)某個(gè)節(jié)點(diǎn)之前其右節(jié)點(diǎn)是否已經(jīng)被訪問(wèn)。

用棧的方式遍歷樹(shù),更加顯式的告訴了大家樹(shù)遍歷的時(shí)候是怎么回溯的,其時(shí)間空間復(fù)雜度仍然是O(n)

Morris遍歷


下面小鹿要給大家介紹一個(gè)神級(jí)樹(shù)遍歷方法Morris,使其空間復(fù)雜度從O(n)變成O(1)!

樹(shù)的遍歷一個(gè)難點(diǎn)就是確定什么時(shí)候以及回溯如何回溯(好像是兩個(gè)點(diǎn)),第一種遞歸的方法通過(guò)函數(shù)的嵌套實(shí)現(xiàn)回溯,第二種方法通過(guò)棧存儲(chǔ)節(jié)點(diǎn)的順序?qū)崿F(xiàn)回溯,而Morris則是利用樹(shù)中空節(jié)點(diǎn)記錄每個(gè)節(jié)點(diǎn)其回溯的節(jié)點(diǎn),實(shí)現(xiàn)空間復(fù)雜度為O(1)的遍歷。

具體來(lái)說(shuō),當(dāng)我們?cè)L問(wèn)了某個(gè)左子樹(shù)最右的節(jié)點(diǎn)后需要回溯到其左子樹(shù)的根節(jié)點(diǎn),但是怎么回去呢,我們需要在之前加一個(gè)鏈接,將該根節(jié)點(diǎn)連到其左子樹(shù)的最右節(jié)點(diǎn)的右節(jié)點(diǎn)上。是不是有點(diǎn)繞呢(╯﹏╰)b,不慌!小鹿帶你一起做一遍中序遍歷的例子就清楚啦~~

以上面的圖為例做中序遍歷,當(dāng)前節(jié)點(diǎn)為6(curr=6)時(shí),找到其左子樹(shù)的最右節(jié)點(diǎn)5,添加鏈接備用,這樣從節(jié)點(diǎn)5我們就可以回溯到節(jié)點(diǎn)6。更新當(dāng)前節(jié)點(diǎn),curr=2,重復(fù)相同的操作。當(dāng)curr=1時(shí),到達(dá)葉子節(jié)點(diǎn),輸出(節(jié)點(diǎn)變藍(lán)),因?yàn)橹疤砑恿随溄?#xff0c;所以可以從節(jié)點(diǎn)1回溯到節(jié)點(diǎn)2,回溯刪除鏈接。當(dāng)前節(jié)點(diǎn)變成4,以此類(lèi)推下去

已經(jīng)懵逼的小伙伴們請(qǐng)仔細(xì)品味下面的代碼( ̄▽ ̄)~

class Solution(object):def __init__(self):self.ans = []def inorderMorris(self, root):p = rootwhile p:if p.left is None:#left-most nodeself.ans.append(p.val)p = p.rightelse:#find prev, the right-most node of the left treeprev = p.leftwhile prev.right and prev.right != p:prev = prev.rightif prev.right is None:#first time to visit pprev.right = p #add linkp = p.leftelse:#second time to visit pself.ans.append(p.val)prev.right = Nonep = p.rightreturn self.ansdef preorderMorris(self, root):p = rootprev = Nonewhile p:if p.left is None:#left-most nodeself.ans.append(p.val)p = p.rightelse:#find right-most node of the left treeprev = p.leftwhile prev.right and prev.right != p:prev = prev.rightif prev.right is None:#first time to visit pprev.right = p #add linkself.ans.append(p.val)p = p.leftelse:#second time to visit pp = p.right #back to rootprev.right?=?None?#delete?the?linkreturn?self.ans??????????????????

Morris前序遍歷和中序遍歷幾乎一模一樣,唯一的差別就是在第一次訪問(wèn)節(jié)點(diǎn)的時(shí)候就輸出,還是第二次訪問(wèn)節(jié)點(diǎn)的時(shí)候輸出。Morris后序遍歷在此基礎(chǔ)上還要稍微的復(fù)雜一丟丟。因?yàn)楹罄m(xù)遍歷根節(jié)點(diǎn)最后輸出,需要增加一個(gè)dump節(jié)點(diǎn)作為假的根節(jié)點(diǎn),使其的左子樹(shù)right-most 指向原來(lái)的根節(jié)點(diǎn)。話(huà)不多說(shuō),我們來(lái)看下代碼吧!

# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val = x # self.left = None # self.right = Noneclass Solution(object):def __init__(self):self.ans = []def postorderMorris(self, root):dump = TreeNode(0)dump.left = rootp = dumpwhile p:if p.left is None:p = p.rightelse:prev = p.leftwhile prev.right and prev.right != p:prev = prev.rightif prev.right is None:#first time to visit pprev.right = pp = p.leftelse:#second time to visit pself.singleLinkReverseTraversal(p.left, prev)prev.right = Nonep = p.rightreturn self.ansdef singleLinkReverseTraversal(self, start, end):#take the right branch from start to end as single link#travel reverselyif start == end:self.ans.append(start.val)returnself.reverse(start, end)curr = endwhile curr != start:self.ans.append(curr.val)curr = curr.rightself.ans.append(curr.val)self.reverse(end, start)def reverse(self, start, end):if start == end:returnprev = Nonecurr = startwhile curr != end:tmp = curr.rightcurr.right = prevprev = currcurr = tmpcurr.right = prev

眼尖的小伙伴看了代碼之后就會(huì)發(fā)現(xiàn),Morris后序遍歷怎么多了兩個(gè)函數(shù),怎么和前序和中序不一樣了呢ヽ(`Д′)ノ!這個(gè)Morris后序遍歷確實(shí)比較挺難理解,我們還是用最簡(jiǎn)單的圖示來(lái)走一遍代碼的意思吧~~~

開(kāi)始除了加了一個(gè)dump節(jié)點(diǎn)以外,都是一樣的一路向下,到達(dá)left-most節(jié)點(diǎn)3,不輸出(注意啦!不一樣啦!)然后回溯到節(jié)點(diǎn)2,逆序輸出從3到3的節(jié)點(diǎn),刪除鏈接。從節(jié)點(diǎn)2一路往右回溯到節(jié)點(diǎn)1,逆序輸出從其左節(jié)點(diǎn)2到prev節(jié)點(diǎn)6,刪除節(jié)點(diǎn)。以此類(lèi)推,就噢啦ヽ( ̄▽ ̄)ノ。

? ? ??

現(xiàn)在大家都清楚了吧~~不清楚的地方歡迎在評(píng)論區(qū)提出噢,小鹿會(huì)盡最大努力為大家答疑解惑嗒( ̄▽ ̄)/后面小鹿鹿鹿還會(huì)持續(xù)推送關(guān)于算法和數(shù)據(jù)結(jié)構(gòu)的小文章,大家有興趣的話(huà)歡迎關(guān)注訂閱哦(′▽`)ノ?。

感謝大家的閱讀[]~( ̄▽ ̄)~*(鞠躬)

總結(jié)

以上是生活随笔為你收集整理的算法与数据结构--空间复杂度O(1)遍历树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。