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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

【剑指offer 07】用迭代和递归两种方法重构二叉树(python实现)

發布時間:2023/12/19 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【剑指offer 07】用迭代和递归两种方法重构二叉树(python实现) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文講解一個經典的面試題,使用 python 通過迭代和遞歸兩種方法重構二叉樹。

題目描述

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如,給出:

前序遍歷 preorder = [3,9,20,15,7] 中序遍歷 inorder = [9,3,15,20,7]

返回如下的二叉樹:

3/ \9 20/ \15 7

限制:0 <= 節點個數 <= 5000。

遞歸方法

二叉樹的前序遍歷順序是:根節點、左子樹、右子樹,每個子樹的遍歷順序同樣滿足前序遍歷順序。

二叉樹的中序遍歷順序是:左子樹、根節點、右子樹,每個子樹的遍歷順序同樣滿足中序遍歷順序。

前序遍歷的第一個節點是根節點,只要找到根節點在中序遍歷中的位置,在根節點之前被訪問的節點都位于左子樹,在根節點之后被訪問的節點都位于右子樹,由此可知左子樹和右子樹分別有多少個節點。

由于樹中的節點數量與遍歷方式無關,通過中序遍歷得知左子樹和右子樹的節點數量之后,可以根據節點數量得到前序遍歷中的左子樹和右子樹的分界,因此可以進一步得到左子樹和右子樹各自的前序遍歷和中序遍歷,可以通過遞歸的方式,重建左子樹和右子樹,然后重建整個二叉樹。

遞歸方法的基準情形有兩個:判斷前序遍歷的下標范圍的開始和結束,若開始大于結束,則當前的二叉樹中沒有節點,返回空值 null。若開始等于結束,則當前的二叉樹中恰好有一個節點,根據節點值創建該節點作為根節點并返回。若開始小于結束,則當前的二叉樹中有多個節點。在中序遍歷中得到根節點的位置,從而得到左子樹和右子樹各自的下標范圍和節點數量,知道節點數量后,在前序遍歷中即可得到左子樹和右子樹各自的下標范圍,然后遞歸重建左子樹和右子樹,并將左右子樹的根節點分別作為當前根節點的左右子節點。

展開來說,以下面的圖為例子:

根據前序遍歷得知,3 是根節點。

根據中序遍歷得知,9 是左子樹且節點數為1,[15, 20, 7] 是右子樹且節點數為3。

根據上一步得到的左子樹右子樹節點個數,可以將前序遍歷劃分為左子樹、右子樹。

前序遍歷中左子樹的節點包含 [9] ,于是 9 就是左子樹根節點。另外,還可以知道左子樹在前序遍歷中的左右邊界為 [1, 1],由于左右邊界相同,說明當前的左子樹其實是葉節點,續劃分左子樹右子樹的話,應該返回None。

前序遍歷中右子樹的節點包含 [20, 15, 7] ,于是 20 是右子樹根節點。另外,右子樹在前序遍歷中的邊界為 [2, 4],左右邊界不相同,需要以20作為根節點繼續劃分子樹。如下圖所示:

中序遍歷中發現 20 的左子樹有一個節點,右子樹有一個節點,因此在前序遍歷中可以確定 [15] 為左子樹,[7] 為右子樹。由于 20 的左右子樹在前序遍歷中邊界相同,所以是葉節點,樹構建完畢。

根據上面的理論,可以得到下面的代碼實現:

class TreeNode:def __init__(self, x):self.val = xself.left = Noneself.right = Noneclass Solution:# 遞歸(1) 56 msdef buildTree(self, preorder, inorder):# 將前序遍歷序列保存為屬性,方便遞歸時得到 valself.po = preorder# 構造一個字典,方便確定邊界self.dic = {}for i in range(len(inorder)):self.dic[inorder[i]] = i# 開始遞歸構造樹return self.recur(0, 0, len(inorder)-1)def recur(self, pre_root, in_left, in_right):# 由于葉子節點沒有左右子樹,弱繼續劃分則應該得到None# 因此,當左邊界大于右邊界時結束遞歸if in_left > in_right:return # 根據根節點的位置得到 valnode = self.po[pre_root]# 構造根節點root = TreeNode(node)# 得到根節點的 val 在中序遍歷序列中的位置i = self.dic[node]# 構造左子樹# 左子樹根節點為前序遍歷序列根節點右邊第一個元素# 右邊界為中序遍歷根節點位置左邊的一個位置root.left = self.recur(pre_root+1, in_left, i-1)# 構造右子樹# (i-in_left) 表示左子樹節點的個數# 右子樹根節點為前序遍歷序列根節點右邊第(i-in_left)+1個元素# 左邊界為中序遍歷根節點位置右邊的一個位置root.right = self.recur(pre_root+(i-in_left)+1, i+1, in_right)return roott = Solution().buildTree([3,9,20,15,7], [9,3,15,20,7]) print(t.val, t.left.val, t.right.val)

時間復雜度 O(N): N 為樹的節點數量。初始化 HashMap 需遍歷 inorder ,占用 O(N) ;遞歸共建立 N 個節點,每層遞歸中的節點建立、搜索操作占用 O(1),因此遞歸占用 O(N)。(最差情況為所有子樹只有左節點,樹退化為鏈表,此時遞歸深度 O(N) ;平均情況下遞歸深度 O(log2Nlog_2 Nlog2?N)。

空間復雜度 O(N): HashMap 使用 O(N) 額外空間;遞歸操作中系統需使用 O(N) 額外空間。

基于遞歸的思想,還有一種寫法:

class Solution:# 遞歸(2) 192 msdef buildTree(self, preorder, inorder):if not preorder:return Noneloc = inorder.index(preorder[0])root = TreeNode(preorder[0])root.left = self.buildTree(preorder[1 : loc + 1], inorder[ : loc])root.right = self.buildTree(preorder[loc+1 : ], inorder[loc+1: ])return root

這樣的寫法更多的使用了python內置的方法,看起來十分簡潔,就是執行時間長了一些。

迭代方法

例如要重建的是如下二叉樹:

3/ \9 20/ / \8 15 7/ \5 10/ 4

其前序遍歷和中序遍歷如下:

preorder = [3,9,8,5,4,10,20,15,7] inorder = [4,5,8,10,9,3,15,20,7]

解題步驟:

  • 使用前序遍歷的第一個元素(例如 3)創建根節點。
  • 創建一個棧,將根節點 (例如 3) 壓入棧內,接下來準備一路向左構建左子樹。
  • 初始化中序遍歷下標為 0。
  • 遍歷 preorder 中還未訪問過的每個元素(例如 [9,8,5,4,10,20,15,7]),判斷棧頂元素是否等于中序遍歷下標指向的元素 (例如 4) 。
  • 若棧頂元素不等于中序遍歷下標指向的元素 (例如 4) ,說明當前的 preorder 元素還沒到達樹的最左端,則將當前元素 (例如 9) 作為其上一個元素(例如棧頂元素 3)的左子節點,并將當前元素 (例如 9) 壓入棧內,繼續對 preorder 中還未訪問過的每個元素(例如 [8,5,4,10,20,15,7]) 重復此過程,一直向左構建子樹 (例如可以得到 3->9->8->5->4),并將構建的子樹入棧 (例如得到棧 [3,9,8,5,4])。
  • 若棧頂元素(即上一次遍歷的元素,例如 4)等于中序遍歷下標指向的元素 (例如 4) ,則從棧內彈出一個元素 (例如 彈出 4),同時令中序遍歷下標指向下一個元素 (例如 5) ,之后繼續判斷棧頂元素 (例如 5) 是否等于中序遍歷下標指向的元素,若相等則重復該操作,直至棧為空或者元素不相等(例如 8 出棧以后,棧頂元素 9 與中序遍歷元素 10 不同),這說明在棧頂元素(例如 8) 產生了分支,因此令當前元素 (例如 10) 作為最后一個相等元素(即剛剛出棧的元素 8 )的右節點。
  • 遍歷結束,返回根節點。

代碼如下:

class TreeNode:def __init__(self, x):self.val = xself.left = Noneself.right = Noneclass Solution:# 迭代 56 msdef buildTree(self, preorder, inorder):if not preorder:return None root = TreeNode(preorder[0])stack = []L = len(preorder)stack.append(root)index = 0 for i in range(1, L):preorderval = preorder[i]# 中序的當前 i 不等于棧頂時表示還沒搜索到根節點,# 每次append的的preorder[i]其實都是其左子樹,左子樹的左子樹,...if stack[-1].val != inorder[index]: #進左子樹node = stack[-1]node.left = TreeNode(preorderval)stack.append(node.left)#一旦中序inorder里的元素與棧頂相等時,表示左子樹已經走完了else:# 如果棧彈空了,表示根節點root出棧了# == 時表示當前出棧元素沒有右孩子while stack and stack[-1].val == inorder[index]:node = stack[-1]stack.pop()index += 1 node.right = TreeNode(preorderval) stack.append(node.right)return root t = Solution().buildTree([3,9,20,15,7], [9,3,15,20,7]) print(t.val, t.left.val, t.right.val)

時間復雜度:O(n) ,前序遍歷和后序遍歷都被遍歷。

空間復雜度:O(n) ,額外使用棧存儲已經遍歷過的節點。

參考文章:

面試題07. 重建二叉樹

迭代-棧圖解:劍指07. (前序中序)重建二叉樹:遞歸

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的【剑指offer 07】用迭代和递归两种方法重构二叉树(python实现)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。