二叉树的递归遍历
- 递归方法论三要素:
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历:中、左、右
中序遍历:左、中、右
后序遍历:左、右、中
-
- 二叉树的前序遍历
-
- 二叉树的后序遍历
以前序遍历为例
146. 参数为当前遍历的节点(中间节点),返回值是一个数组,用来存放遍历节点的数值
147. 终止条件为当前遍历的节点为空
148. 单层递归的逻辑,即为先遍历中间节点,再左节点(遍历到的左节点如果不是叶子节点,将继续遍历该节点构成的左子树),最后右节点(同理右子树),所以递归用在了这,每遍历一个节点,都需要遍历其左子节点和右子节点
# 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: Optional[TreeNode]) -> List[int]:
res = [] # 用来存放结果
# 定义递归函数
def travelsal(cur): # 这里也可以传入res,也可以不用,res是递归函数外部的变量
# 终止条件
if cur == None:
return
res.append(cur.val) # 中
travelsal(cur.left) # 左
travelsal(cur.right) # 右
travelsal(root)
return res
# 中序遍历
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def travelsal(root):
# 终止条件
if root == None:
return
travelsal(root.left) # 左
res.append(root.val) # 中
travelsal(root.right) # 右
travelsal(root)
return res
# 后序遍历
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def travelsal(root):
if root == None:
return
travelsal(root.left) # 左
travelsal(root.right) # 右
res.append(root.val) # 中
travelsal(root)
return res
看起来还算简单,掌握一种,其他的就都会写了,还是要理解底层逻辑
二叉树的迭代遍历
(考察非递归遍历时)
递归的底层逻辑就是栈,所以迭代遍历我们用栈来模拟递归
- 前序遍历,中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
先加入右孩子再加入左孩子是因为栈先弹出左孩子,也就是先处理左孩子
# 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: Optional[TreeNode]) -> List[int]:
# 根结点为空则返回空列表
if not root:
return []
res = []
stack = [root]
while stack:
node = stack.pop()
# 先处理中间节点
res.append(node.val)
# 右孩子先入栈
if node.right:
stack.append(node.right)
# 左孩子后入栈
if node.left:
stack.append(node.left)
return res
- 后序遍历,只用改动前序遍历左右孩子入栈的顺序,最后再反转存储结果的数组,左右中 等同于 反转 中右左
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 左孩子先入栈
if node.left:
stack.append(node.left)
# 右孩子后入栈
if node.right:
stack.append(node.right)
# 将最终的数组翻转
return result[::-1]
- 中序遍历 稍微复杂,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = []
cur = root
while cur or stack:
if cur:
stack.append(cur)
cur = cur.left # 访问到左子树的最底部的左叶子节点,再开始处理
else:
cur = stack.pop()
res.append(cur.val)
# 加入左节点,然后看其有无右孩子,没有右孩子,此时会弹出上一次遍历到的中间节点,然后访问中间节点的右孩子
# 就达到了 左中右 的效果
cur = cur.right
return res
二叉树的统一迭代法
用栈实现中序遍历,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
与前序后序代码风格不一致,但是我们也可以给出统一迭代法,这里统一是指代码逻辑一致
实现方法就是 将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
- 中序
# 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: Optional[TreeNode]) -> List[int]:
res = []
stack = []
if root:
stack.append(root)
while stack:
node = stack.pop()
if node:
if node.right:
stack.append(node.right) # 先加右节点
stack.append(node) # 把中间节点弹出了,再给它加回去,顺便做标记
stack.append(None)
if node.left:
stack.append(node.left) # 最后加左节点
else:
node = stack.pop()
res.append(node.val)
return res
前序 中左右 对应 入栈顺序右左中
后序 左右中 对应 入栈顺序中右左