前序/中序/後序/層序遍歷(非遞歸版本)
遞歸版本見註釋部分代碼
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
迭代方法使用棧或者隊列。當前訪問的節點一定是棧頂的節點(都是先將根節點入棧),先取出,再按照遍歷順序將其他節點入棧。
①前序遍歷
這個方法是先讀取根節點結果,右子樹、左子樹分別入棧,出棧輸出結果則是根->左->右
class Solution:
# def __init__(self):
# self.res = []
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 遞歸
# if root:
# self.res.append(root.val)
# self.preorderTraversal(root.left)
# self.preorderTraversal(root.right)
# return self.res
# 棧
res = []
if not root: return res
s = [root]
while s:
node = s.pop()
res.append(node.val)
if node.right: s.append(node.right)
if node.left: s.append(node.left)
return res
② 中序遍歷
只對根節點做出入棧的處理。根先入棧,遍歷左子樹,根出棧,遍歷右子樹,則輸出結果爲左->根->右。
class Solution:
# def __init__(self):
# self.res = []
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 遞歸
# if root:
# self.inorderTraversal(root.left)
# self.res.append(root.val)
# self.inorderTraversal(root.right)
# return self.res
# 棧
res = []
if not root :return res
s = []
while s or root:
if root:
s.append(root)
root = root.left
else:
root = s.pop()
res.append(root.val)
root = root.right
return res
③後序遍歷
巧妙借用前序的思想,先讀取根節點結果,左子樹、右子樹依次入棧,則輸出爲根->右->左,最後將輸出逆序就能生成正確的後序輸出:左->右->根。
class Solution:
# def __init__(self):
# self.res = []
def postorderTraversal(self, root):
# 遞歸
# if root:
# self.postorderTraversal(root.left)
# self.postorderTraversal(root.right)
# self.res.append(root.val)
# return self.res
# 棧
res = []
if not root: return res
s = [root]
while s:
node = s.pop()
res.append(node.val)
if node.left: s.append(node.left)
if node.right: s.append(node.right)
return res[::-1]
還有一種更容易理解的方法:“顏色標記法”,兼具棧迭代方法的高效,又像遞歸方法一樣簡潔易懂,更重要的是,這種方法對於前序、中序、後序遍歷,能夠寫出完全一致的代碼。
其核心思想如下:
- 使用顏色標記節點的狀態,新節點爲白色,已訪問的節點爲灰色。
- 如果遇到的節點爲白色,則將其標記爲灰色,然後將其右子節點、自身、左子節點依次入棧。
- 如果遇到的節點爲灰色,則將節點的值輸出
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
WHITE, GRAY = 0, 1
stack, res = [(WHITE, root)], []
while stack:
color, node = stack.pop()
if node is None:
continue
if color == WHITE:
stack.append((GRAY, node)) #前序/中序/後序調整下入棧的順序就可以。因爲出棧順序跟入棧的相反,而前中後都是先左後右,只是根的位置不同,所以右都比左先入棧。這行放最前就是後序,放中間就是中序,放最後就是前序。
stack.append((WHITE, node.right))
stack.append((WHITE,node.left))
else:
res.append(node.val)
return res
④ 層序遍歷
用隊列的方法,每次訪問隊首節點,並將其左、右子樹入隊。
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
q, res = [root], []
while q:
level = []
tmp_q, q = q, []
for node in tmp_q:
if node:
level.append(node.val)
q.append(node.left)
q.append(node.right)
if level:
res.append(level)
return res
2)遞歸方法中的自頂向下 / 自底向上
舉例:求二叉樹最大深度
① 自頂向下( 通過函數傳值的方法,從頂層傳到底層)
class Solution:
def maxDepth(self, root: TreeNode) -> int:
return self.dfs(root,0)
def dfs(self, root, depth):
if not root: return depth
l_depth = self.dfs(root.left,depth+1)
r_depth = self.dfs(root.right,depth+1)
return max(l_depth,r_depth)
② 自底向上
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
l_depth = self.maxDepth(root.left)
r_depth = self.maxDepth(root.right)
return max(l_depth,r_depth)+1
練習:
1)對稱二叉樹
思路:
- 它們的兩個根結點具有相同的值;
- 每個樹的右子樹都與另一個樹的左子樹鏡像對稱。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
return self.dfs(root.left,root.right)
def dfs(self,node1,node2):
if node1 == None and node2 == None : #左、右都爲葉子節點
return True
elif node1 == None : #左、右僅一方爲葉子節點
return False
elif node2 == None:
return False
else:
return (node1.val == node2.val) \
and self.dfs(node1.left,node2.right) \
and self.dfs(node1.right,node2.left)
2)路徑總和
給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上所有節點值相加等於目標和。
思路:自頂向下,向葉子節點傳值
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
sum -= root.val
if not root.left and not root.right: # 葉子節點計算結果
return sum == 0
return self.hasPathSum(root.left,sum) \
or self.hasPathSum(root.right,sum) #自頂向下,向葉子節點傳值
3)從中序與後序遍歷序列構造二叉樹
注:題目規定,二叉樹中沒有重複的元素。
思路:
- 後序遍歷最後一個爲root節點
- 根據root節點在中序遍歷的位置i,可以確定:
中序遍歷中,inorder[:i]爲左子樹, inorder[i+1:]爲右子樹;
後序遍歷中,postorder[:i]爲左子樹,postorder[i:len(postorder)-1]爲右子樹。
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
if not inorder or not postorder:
return None
root_val = postorder[-1] # 後序遍歷最後一個爲root
root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
root.left = self.buildTree(inorder[:root_inorder_idx],postorder[:root_inorder_idx])
root.right = self.buildTree(inorder[root_inorder_idx+1:],postorder[root_inorder_idx:len(postorder)-1])
return root
4)從前序與中序遍歷序列構造二叉樹
思路:
前序遍歷第一個爲root節點。
根據root節點在中序遍歷的位置i,可以確定:
中序遍歷中,inorder[:i]爲左子樹, inorder[i+1:]爲右子樹
前序遍歷中,preorder[1:i+1]爲左子樹,preorder[i+1:]爲右子樹。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not inorder or not preorder:
return None
root_val = preorder[0] # 前序遍歷第一個爲root
root, root_inorder_idx = TreeNode(root_val), inorder.index(root_val)
root.left = self.buildTree(preorder[1:root_inorder_idx+1],inorder[:root_inorder_idx])
root.right = self.buildTree(preorder[root_inorder_idx+1:],inorder[root_inorder_idx+1:])
return root
5)填充每個節點的下一個右側節點指針(滿二叉樹)
填充它的每個 next 指針,讓這個指針指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指針設置爲 NULL。初始狀態下,所有 next 指針都被設置爲 NULL。
方法1:層次遍歷,無論二叉樹怎麼變化都通用,空間複雜度O(n)。
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root: return None
q, res = [root], []
while q:
level = []
len_q = len(q)
for i, node in enumerate(q):
if i == len_q-1:
node.next = None
else:
node.next = q[i+1]
tmp_q, q = q, [] #更新下一層隊列
for node in tmp_q:
if node.left: #確保隊列中是非空節點
q.append(node.left)
if node.right:
q.append(node.right)
return root
方法2:觀察到滿二叉樹的next是固定的,即left.next是root.right,right.next是root.next.left。(root表示爲父節點)
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return None
if root.left:
root.left.next = root.right
if root.right and root.next:
root.right.next = root.next.left
self.connect(root.left)
self.connect(root.right)
return root
6)填充每個節點的下一個右側節點指針II(非滿二叉樹)
和滿二叉樹不同的是,next不是固定的:
- left.next 可能是root.right,或者需要不斷地從root.next按從左到右的順序查找。
- right.next同樣,需要不斷地從root.next按從左到右的順序查找。
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root: return None
if root.left:
if root.right:
root.left.next = root.right
else:
root.left.next = self.look_for_father_next(root)
if root.right:
root.right.next = self.look_for_father_next(root)
self.connect(root.right) # 因爲需要查找父節點的next,遞歸順序先右再左
self.connect(root.left)
return root
def look_for_father_next(self, root):
if not root.next:
return None
while root.next: #不斷地從父節點的next節點按從左到右的順序查找
if root.next.left:
return root.next.left
if root.next.right:
return root.next.right
root = root.next
return None
7) 二叉樹的最近公共祖先
思路:
由於每個節點只有唯一一個父節點,我們可以使用到字典的value-key的形式(節點-父節點)字典中預置根節點的父節點爲None。
字典建立完成後,二叉樹就可以看成一個所有節點都將最終指向根節點的鏈表了。於是在二叉樹中尋找兩個節點的最小公共節點就相當於,在一個鏈表中尋找他們相遇的節點。(參考leetcode 160.相交鏈表)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
father_dict = {root:None}
def dfs(node):
if node:
if node.left:
father_dict[node.left] = node
if node.right:
father_dict[node.right] = node
dfs(node.left)
dfs(node.right)
dfs(root)
l1, l2 = p, q
while l1 != l2 : #於是在二叉樹中尋找兩個節點的最小公共節點就相當於,在一個鏈表中尋找他們相遇的節點(假定最近公共祖先存在)
l1 = father_dict.get(l1, q)
l2 = father_dict.get(l2, p)
return l1
8)二叉樹的序列化與反序列化
思路:
題目要求對層次遍歷做序列化和反序列化,可以藉助兩個隊列,一個保存父節點,一個保存孩子節點,一層層更新。
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
q, res = [root], []
if not root:
return res
while q:
tmp_q, q = q, []
for node in tmp_q:
if node:
res.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
res.append('null')
while res[-1] == 'null': #刪除末尾的null
res.pop()
return ','.join(res) #題目要求返回str
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if type(data) == str:
data = data.split(',')
length = len(data)
if not length:
return None
root = TreeNode(data[0])
parents, childs = [root], []
i = 1 # 孩子結點在data中的索引
while i < length:
for parent in parents:
if i < length and data[i]: #更新左子樹
node = TreeNode(data[i])
parent.left = node
childs.append(node)
i += 1
if i < length and data[i]: #更新右子樹
node = TreeNode(data[i])
parent.right = node
childs.append(node)
i += 1
if childs: # 更新下一層,將childs作爲新的fathers
parents = childs
childs = []
return root