文章目录
引言
二叉树遍历即将二叉树按照某种顺序访问一遍,涉及深度优先遍历与广度优先遍历。深度优先遍历可分为前序遍历、中序遍历、后序遍历。广度优先遍历包括层次遍历,层序遍历可分为自顶向下层序遍历,自底向上层序遍历,锯齿层序遍历。
一、深度优先遍历
一般都有递归与迭代思路,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同
144. 二叉树的前序遍历
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例:
输入:root = [1,null,2,3]
输出:[1,2,3]
思路1:递归
前序遍历操作定义为:若二叉树为空,为空操作;否则
根 -> 左 -> 右
复杂度分析:
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为O(n)。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if root == None:
return []
result = [root.val]
# 递归调用左节点
left_item = self.preorderTraversal(root.left)
# 递归调用右节点
right_item = self.preorderTraversal(root.right)
return result + left_item + right_item
思路2:迭代
递归算法使用系统栈,不好控制,性能问题比较严重,需要进一步了解不用递归如何实现。
为了维护固定的访问顺序,使用栈数据结构的后入先出特性。
先处理根节点,根据访问顺序根→左→右,先入栈的后访问,为了保持访问顺序(先入后出),先把右孩子入栈,再入栈左孩子(此处需要注意:出栈才是访问顺序)
复杂度分析:
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if root == None:
return []
stack = [root]
result = []
while stack:
# 弹出元素—后入先出
node = stack.pop()
if node != None:
# 先入栈右节点
stack.append(node.right)
# 再入栈左节点
stack.append(node.left)
# 保存遍历的值(栈顶元素的值)
result.append(node.val)
return result
思路3:颜色标记法
其核心思想是使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色:
如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、左子节点、自身依次入栈。
如果遇到的节点为灰色,则将节点的值输出。
颜色标记法的优势:兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
"""
颜色标记法
"""
if root == None:
return []
write,gray = 0,1
stack = [(write,root)]
result = []
while stack:
color,node = stack.pop()
if node != None:
# 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、左子节点、自身依次入栈。
if color == write:
stack.append((write,node.right))
stack.append((write,node.left))
stack.append((gray,node))
# 如果遇到的节点为灰色,则将节点的值输出。
else:
result.append(node.val)
return result
思路4:Morris遍历
Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。
- 新建临时节点,令该节点为 root;
- 如果当前节点的左子节点为空,将当前节点加入答案,并遍历当前节点的右子节点;
- 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点:
如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点。然后将当前节点加入答案,并将前驱节点的右子节点更新为当前节点。当前节点更新为当前节点的左子节点。
如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = list()
if not root:
return res
p1 = root
while p1:
p2 = p1.left
if p2:
while p2.right and p2.right != p1:
p2 = p2.right
if not p2.right:
res.append(p1.val)
p2.right = p1
p1 = p1.left
continue
else:
p2.right = None
else:
res.append(p1.val)
p1 = p1.right
return res
复杂度分析:
- 时间复杂度:O(n),其中 n 是二叉树的节点数。没有左子树的节点只被访问一次,有左子树的节点被访问两次。
- 空间复杂度:O(1)。只操作已经存在的指针(树的空闲指针),因此只需要常数的额外空间。
94. 二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
思路1:递归
中序遍历操作定义为:若二叉树为空,为空操作(递归终止条件);否则
左 -> 根 -> 右
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 递归
if root == None:
return []
result = [root.val]
left_item = self.inorderTraversal(root.left)
right_item = self.inorderTraversal(root.right)
return left_item + result + right_item
思路2:迭代
核心思路依旧是利用栈维护节点的访问顺序:
左→根→右
。
- 访问当前节点,
如果当前节点有左孩子,则把它的左孩子都入栈,移动当前节点到左孩子,重复第一步直到当前节点没有左孩子 - 当当前节点没有左孩子时,栈顶节点出栈,加入结果数组
- 当前节点指向栈顶节点的右节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 迭代
if root == None:
return []
stack