二叉樹是一種更為典型的樹樹狀結構。如它名字所描述的那樣,二叉樹是每個節點最多有兩個子樹的樹結構,通常子樹被稱作「左子樹」和「右子樹」。下面是個二叉樹的例子:
用python定義二叉樹的節點:
# 二叉樹節點
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
前序遍歷訪問的順序為:根節點、左子樹、右子樹。對於上圖,遍歷過程如下:
一開始指向根節點,訪問它,為E
有左子樹,指向他並訪問,為A
A沒有左子樹,指向其右子樹並訪問,為C
C有左子樹和右子樹,按順序訪問,為BD
E的左半邊訪問完畢,指向E的右子樹並訪問,為G
G只有右子樹,訪問它,為F
F往下已經沒有葉子節點,遍歷結束
因此,前序遍歷的結果為:EACBDGF
2、中序遍歷中序遍歷訪問的順序為:左子樹、根節點、右子樹。對於上圖,遍歷過程如下:
因此,中序遍歷的結果為:ABCDEGF。通常來說,對於二叉搜索樹,我們可以通過中序遍歷得到一個遞增的有序序列
3、後序遍歷後序遍歷訪問的順序為:左子樹、右子樹、根節點。對於上圖,遍歷過程如下:
因此,後序遍歷的結果為:BDCAFGE。當你刪除樹中的節點時,用到後序遍歷。也就是說,當你刪除一個節點時,你將首先刪除它的左節點和它的右邊的節點,然後再刪除節點本身。
4、層序遍歷層序遍歷就是逐層遍歷樹結構,下圖展示了它的層次結構:
二叉樹的層序遍歷即為廣度優先搜索,該算法從一個根節點開始,首先訪問節點本身。然後遍歷它的相鄰節點,其次遍歷它的二級鄰節點、三級鄰節點,以此類推。廣度優先搜索需要用到隊列。遍歷過程如下:
初始化隊列q=[],並將根節點E入隊,為q=[E]
q出隊,為E
E有左子樹和右子樹,兩者入隊,為q=[A, G]
q出隊,為A,此時將A的右子樹入隊,為q=[G, C]
q出隊,為G,此時G的右子樹入隊,為q=[C, F]
q出隊,為C,此時C的左子樹、右子樹入隊,為q=[F, B, D]
q出隊,為F,此時q=[B, D]
F沒有子樹,q繼續出隊,為B,此時q=[D]
q出隊,為D
q為空,遍歷結束
因此,層序遍歷的結果為:EAGCFBD。
三、程序實現二叉樹的前序、中序、後序和層序遍歷,分別為leetcode的第144、94、145、102題,都可以用遞歸和迭代兩種方法做。
1、遞歸實現對於前序、中序和後序來說,遞歸的現實非常簡單,他們的實現區別是根節點訪問的順序不一樣。代碼如下:
# 前序遍歷遞歸
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
else:
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
# 中序遍歷遞歸
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
else:
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
# 後序遍歷遞歸
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
else:
return self.postorderTraversal(root.left) + self.postorderTraversal(root.right)+ [root.val]
可以看出不同的地方就是[root.val]的位置不一樣,前序遍歷[root.val]就在最一開始,中序遍歷[root.val]則在中間,後序遍歷[root.val]就在最後。整個迭代結構下圖所示:
對於層序遍歷的遞歸實現,我們不僅要輸出層序遍歷的序列,還要有每個元素屬於哪個層次的信息,可以用列表嵌套的方式,比如之前的例子裡,層序遍歷表示為[[E], [AG], [CF], [BD]]。
# 層序遍歷遞歸
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
levels = []
if not root:
return levels
def helper(node, level):
# start the current level
if len(levels) == level:
levels.append([])
# append the current node value
levels[level].append(node.val)
# process child nodes for the next level
if node.left:
helper(node.left, level + 1)
if node.right:
helper(node.right, level + 1)
helper(root, 0)
return levels
我們設更節點的層級序號為0,依次往下為1、2、3……。leaves為保存結果的列表,每一層的結果又為一個列表,保存在leaves裡,層級序號即為在leaves裡的index,在第i層級,leaves裡應該有i+1個列表,第i+1個列表為當前層級的節點。每個列表只append層級序號相同的節點。
2、迭代實現二叉樹的迭代實現都需要用到棧。對於前序、中序、後序遍歷,他們的迭代實現大同小異:
# 前序遍歷迭代
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return []
stack, output = [root, ], []
while stack:
root = stack.pop()
if root is not None:
output.append(root.val)
if root.right is not None:
stack.append(root.right)
if root.left is not None:
stack.append(root.left)
return output
# 中序遍歷迭代
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return []
stack, output= [], []
curr = root
while curr or len(stack) > 0:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
output.append(curr.val)
curr = curr.right
return output
# 後序遍歷迭代
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return []
stack, output = [root, ], []
while stack:
root = stack.pop()
output.append(root.val)
if root.left is not None:
stack.append(root.left)
if root.right is not None:
stack.append(root.right)
return output[::-1]
對於前序遍歷,先初始化一個有根節點的棧,然後進入循環,出棧根節點,訪問它。然後依次入棧右子樹、左子樹(注意棧是先進後出)。則下次循環,會先出棧左子樹,訪問它,然後入棧它的右子樹、左子樹。這樣根節點E的右子樹會在其左子樹遍歷完後才會出棧。循環停止條件是棧為空。
對於中序遍歷,因為要優先遍歷左子樹,循環之前初始化一個空棧,進入循環後,用一個內循環依次入棧左子樹,直到葉子節點(也為一個左子樹),然後退出內循環,出棧並訪問。
對於後序遍歷,和前序遍歷的整體結構是一樣的,唯一不同的是在循環裡先入棧左子樹,後入棧右子樹,循環結束後將結果逆序。
對於層序遍歷的迭代實現,循環之前初始化一個有根節點的隊列,此時隊列只有一個元素,進入循環後,出隊並訪問,然後將根節點的左右子樹依次入隊。下次循環,依次將上次隊列裡的元素出隊,同隊入隊他們的左右子樹。這樣每次循環只出隊當前層次的節點,併入隊他們的左右子樹,完成了層次之間的交換。這實際上是一種廣度優先搜索。層序遍歷迭代寫法:
# 層序遍歷迭代,使用棧
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
q = [root]
result = []
while q:
res = []
for i in range(len(q)):
node = q.pop(0)
res.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
result.append(res)
return result
# 層序遍歷迭代,使用隊列
from collections import deque
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
res = []
q = deque()
q.append(root)
while q:
size = len(q)
curr_level = []
for _ in range(size):
node = q.popleft()
curr_level.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
res.append(curr_level)
return res
二叉樹的最近公共祖先
此題為leetcode第236題
(https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 樹為空返回None
if root is None:
return None
# 找到了p或q
if root == p or root == q:
return root
# 在root的左子樹找p或q
left = self.lowestCommonAncestor(root.left, p, q)
# 在root的右子樹找p或q
right = self.lowestCommonAncestor(root.right, p, q)
if left is None: # 如果左子樹沒有p或q,返回右孩子
return right
elif right is None: # 如果右子樹沒有p或q,返回左孩子
return left
else: # 如果分別在左右子樹找到了p或q,那麼root就是最近公共祖先
return root
(全文完)
作者簡介:
李旭東,中科院在讀研究生,研究方向為深度學習、工業故障診斷,喜歡讀書,希望同各位小夥伴一起交流學習!
作者微信號:wuan3076
個人網站:lixudong.ink
博客:https://blog.csdn.net/u014157632
郵箱:lixudong16@mails.ucas.edu.cn
粉絲福利:
關注公眾號「辰語學習筆記」,在公眾號對話框回復關鍵詞「知識手冊」,免費獲取《機器學習基礎知識手冊》!
註:這篇文章是公眾號粉絲投稿的原創文章,想要投稿請聯繫小編微信:makersky1688
往期回顧
【原創首發】《機器學習基礎知識手冊》:一個總結性的機器學習基礎知識小冊子
【原創首發】創意科普魔方,不止是一個玩具!
生成「星辰大海」:變分自編碼器(VAE)實踐
零基礎學爬蟲(一):不用編程抓取B站彈幕信息
零基礎學爬蟲(二):幾分鐘完成你的第一個爬蟲程序!
零基礎學爬蟲(三):抓取網頁的多個元素
零基礎學爬蟲(四):不規則分頁的抓取和反爬蟲應對方法
手把手教你搭建谷歌TensorFlow深度學習開發環境!