递归实现树的遍历十分简单 本篇文章总结 树的迭代遍历
前序遍历:
辅助栈: 先压右 再压左 所以 先出左 再出右 很好理解 配合代码如下
if not root: return []
stack = [root]
res = []
while stack:
# 除了根节点 根据之前压栈的规则 如果某个节点存在左子节点 那么该左子节点一定首先出栈
# 左子节点出栈之后 如果该节点存在右子节点 右子节点一定紧随出栈 因为他们是紧密连续入栈的
# 这样就能做到左边的子树始终在栈尾 首先被pop出来
# 起初被压栈的右子树的根节点始终被压制 直到左子树的所有元素被遍历之后 才开始遍历右子树
# 上面一行话对理解迭代做法非常重要,在任何节点 左子树的元素都比右子树的元素更先遍历
# 因为 右子树的根节点始终被左子树的根节点压着
node = stack.pop()
res.append(node.val)
# 先将右节点入栈 再将左节点入栈
if node.right: stack.append(node.right)
if node.left: stack.append(node.left)
print(res)
也可以这样写:
if not root: return []
stack = [root]
node = root
while stack or node:
# 从根节点开始 向左边遍历 并将遍历的所有节点压栈
while node:
res.append(node.val)
stack.append(node)
node = node.left
# 直到遇到最左边的节点node
node = stack.pop()
# 最左边的节点node的右节点继续上述操作 如果最左边的节点是叶节点
# 那么 之后的while node: 直接跳过 更新node = stack.pop()为第二左边的节点
# 对第二左边的节点的右节点继续上述操作, 同样的如果第二左边的节点没有右节点
# 继续更新 node为第三左边的节点 直到将node更新为根节点root
# 此时stack为空,但是node不为空
# 再将node更新为root.right 如果root.right为空 那么循环结束
# 如果root.right不为空while node:不能跳过 即此时我们对根节点的右子树继续了我们
# 之前的所有操作
node = node.right
print(res)
这样写的思想是,保留了子节点与父节点之间的联系(通过node = stack.pop()),当一直往左遍历到头了之后,回到父节点对父节点的右子树进行同样的,从父节点的右节点开始一直往左遍历。
可以思考一下这个代码和之前的代码有什么不同。
中序遍历,代码:
if not root: return []
stack = []
node = root
while stack or node:
# 一直往左找 但是 在这个过程中暂时先不管节点的值 而是先将这些节点压栈
while node:
stack.append(node)
node = node.left
# 找到最左边的节点之后,将第一个结果放入到res中
node = stack.pop()
res.append(node.val)
# 如果最左边的节点有右节点 那么继续对右节点进行上述操作
# 如果最左边的节点是叶节点,那么之后node = stack.pop()就是该节点的父节点
# 继续对该父节点判断 如果该父节点 有右节点 那么继续对右节点进行上述操作
node = node.right
print(res)
中序遍历的核心是 先将根节点和根节点往左一直遍历的节点压栈(第一次压栈),出栈一个节点,记录该节点的值,再将该节点的右节点和该右节点往左一直遍历的节点压栈(如果右节点不为空,第二次压栈),这样就能实现中序遍历。
后序遍历:
代码:
if not root: return []
stack = [root]
node = root
while stack or node:
# 从根节点开始 向右边遍历 并将遍历的所有节点压栈
while node:
res.append(node.val)
stack.append(node)
node = node.right
# 直到遇到最右边的节点node
node = stack.pop()
# 最右边的节点node的左节点继续上述操作 如果最右边的节点是叶节点
# 那么 之后的while node: 直接跳过 更新node = stack.pop()为第二右边的节点
# 对第二右边的节点的左节点继续上述操作, 同样的如果第二右边的节点没有左节点
# 继续更新 node为第三右边的节点 直到将node更新为根节点root
# 此时stack为空,但是node不为空
# 再将node更新为root.left如果root.left为空 那么循环结束
# 如果root.left不为空while node:不能跳过 即此时我们对根节点的左子树继续了我们
# 之前的所有操作
node = node.left
# 最后将结果倒序返回即可
print(res[::-1])
这是比较取巧的方法,模拟栈的另外一个方法是:
if not root: return []
stack, res = [(0,root)], []
while stack:
flag, node = stack.pop()
if not node: continue
if flag == 1:
res.append(node.val)
else:
stack.append((1,node))
stack.append((0,node.right))
stack.append((0,node.left))
print(res)
在入栈时加入标识,当标识为1的时候才将该节点的值放入结果中,如果不唯一,就将该节点重新压栈标识改为1并且将左右子节点也压栈。
也可以这样写:
res = []
stack = []
prev = None
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
if not root.right or root.right == prev:
res.append(root.val)
prev = root
root = None
else:
stack.append(root)
root = root.right
return res
注意这三种迭代写法可以总结出一个模板,只要观察出节点值的打印顺序导致的while循环体改变的规律即可快速记忆,可以自己想想~
Morris遍历已经更新: