算法随笔-二叉树遍历的N种姿势
最近在练习用Python刷算法,leetcode上刷了快300题。一开始怀疑自己根本不会写代码,现在觉得会写一点点了,痛苦又充实的刷题历程。对我这种半路出家的人而言,收获真的很大。
今天就从二叉树遍历写起,曾经有次面试就被迭代实现卡过。。。

最简单的递归
#先序遍历
def preorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
def preTraversal(node,result):
if node==None:
return
#输出放在最前
result.append(node.val)
preTraversal(node.left,result)
preTraversal(node.right,result)
preTraversal(root,res)
return res
#中序遍历
def inorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
def inorderTraversal(node,result):
if node==None:
return
inorderTraversal(node.left,result)
#输出放在中间
result.append(node.val)
inorderTraversal(node.right,result)
inorderTraversal(root,res)
return res
#后序遍历
def postorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
def postTraversal(node,result):
if node==None:
return
postTraversal(node.left,result)
postTraversal(node.right,result)
#输出放在最后
result.append(node.val)
postTraversal(root,res)
return res
递归实现极其简单,但是面试时往往会更进一步,问几个深入一点的问题:
- 递归实现存在什么问题?
- 尾递归是什么?
这两个问题都在讲一件事,就是迭代实现的二叉树遍历会存在StackOverflow异常。却决于操作系统和运行时,不同的程序拥有的栈大小不一,但是栈的容量都是较小的,基于递归的实现,如果未优化,就会导致堆栈溢出的异常。对.NET而言,系统分配的默认栈空间大小是1MB,树节点一多,很容就满了。
而尾递归则是对普通递归的优化,每次迭代最后都是直接调用自身。很多编译器都对尾递归做了生成优化,使得它可以不在调用栈上面每次都添加一个新的堆栈帧,而是更新它。这样就不会导致调用栈爆炸的异常。
如果你能答上上面的问题,往往会让你写一下二叉树遍历非递归的实现,这里难度就上了一个台阶。
非递归实现
二叉树迭代一定会用到栈!二叉树迭代一定会用到栈!二叉树迭代一定会用到栈!
talk is easy, show you my code
#超简单的先序遍历
def preorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
if root==None:
return
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
#稍复杂的中序遍历
def inorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
curr=root
stack=[]
while curr or stack:
#优先遍历全部左节点
while curr:
stack.append(curr)
curr=curr.left
node=stack.pop()
res.append(node.val)
#当前节点切换到右节点
if node.right:
curr=node.right
return res
#最复杂的后序遍历
#解法1 基于先序遍历的变形 leetcode官方题解:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/er-cha-shu-de-hou-xu-bian-li-by-leetcode/
def postorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
if root==None:
return res
stack=[root]
while stack:
node=stack.pop()
res.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
#反转结果
return res[::-1]
#解法2 记录走过的路径
def postorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
if root==None:
return res
stack=[root]
km=set()
while stack:
node=stack[-1]
#只有叶子节点和左右节点被遍历过的才可以输出
if (node.left==None and node.right==None) or (node.left in km or node.right in km):
res.append(node.val)
km.add(node)
stack.pop()
else:
#注意进栈顺序
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return res
#解法3 中序遍历的变形,左右子树遍历切换
def postorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
if root==None:
return res
stack=[]
curr=root
last=None
while curr or stack:
if curr:
stack.append(curr)
#切换到左子树
curr=curr.left
else:
node=stack[-1]
#是否切换到右子树
if node.right and node.right!=last:
curr=node.right
else:
res.append(node.val)
stack.pop()
last=node
return res
后序遍历的几种迭代解法非常值得一看,想起当初在白板面前呆了半天也没写出来,蓝廋香菇,现在可以自信地讲树的遍历我掌握了!!!

浙公网安备 33010602011771号