前序/中序/后序/层序遍历(非递归版本)
递归版本见注释部分代码
# 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