树的四种遍历的递归和非递归方法(LC144、LC94、LC145、LC102)(hot100)
例子:
遍历顺序:
前序遍历 (根左右)[root | left | right]: 1-2-3-7-5-3-6
中序遍历 (左根右)[left | root | right]: 4-7-2-5-1-6-3
后序遍历 (左右根)[left | right | root]: 7-4-5-2-6-3-1
递归方法
思考:(设当前节点为root)
递归结束条件:判断root是否为空,如果为空则返回。
递归:
对于前序:添加root的值→递归root.left→递归root.right
对于中序:递归root.left→添加root的值→递归root.right
对于后序:递归root.left→递归root.right→添加root的值
代码:
# 前序递归遍历
def preorderRecur(root):
res = []
def recur(node):
if not node:
return
res.append(node.val)
recur(node.left)
recur(node.right)
recur(root)
return res
# 中序递归遍历
def inorderRecur(root):
res = []
def recur(node):
if not node:
return
recur(node.left)
res.append(node.val)
recur(node.right)
recur(root)
return res
# 后序递归遍历
def lateorderRecur(root):
res = []
def recur(node):
if not node:
return
recur(node.left)
recur(node.right)
res.append(node.val)
recur(root)
return res
非递归方法(迭代方法)
迭代方法其实是对于递归方法的一个展开。利用了栈先进后出的特性来模拟递归的过程。其整个的过程还是先根部 左子树 右子树 只是入栈或者添加返回值时的位置不同。
对于前序的思考:
前序的遍历顺序为:根左右( 1-2-4-7-5-3-6)
思想过程:
① res记录遍历结果, s 作为栈来模拟递归。
② 当root不为空或者s不空时执行③(代表仍有元素未遍历到)
③ 若root不为空,添加root值到res中(遍历根1 2 4);将root加入到s;将root.left赋值给root (一直遍历左子树。对于给定例子 s=[1,2,4])
④ 若root空,则将 新root pop出来(第一次pop的是4);并将root.right赋值给root.(当前root右子树,root=7)
代码:
def preorderNoRecur(root):
res, s = [], []
while root or s:
# 当前节点不为空
if root:
res.append(root.val) # 添加返回值位置 因为先遍历根部
s.append(root) # 入栈
root = root.left # 遍历左节点
# 如果当前节点为空
else:
root = s.pop() # 出栈
root = root.right # 遍历右节点
return res
对于中序的思考:
中序的遍历顺序为:左根右( 4-7-2-5-1-6-3)
与前序迭代过程不同的是:添加根节点的位置不应该是入栈前,而应该遍历完左节点出栈时。
思想过程:
① res记录遍历结果, s 作为栈来模拟递归。
② 当root不为空或者s不空时执行③(代表仍有元素未遍历到)
③ 若root不为空,则将root加入到s,并将root.left赋值给root (一直遍历左子树。对于给定例子 s=[1,2,4])
④ 若root空,则将 新root pop出来,添加root值到res中(遍历根 第一次为4)。并将root.right赋值给root。(当前root右子树,root=7)
代码:
def inorderNoRecur(root):
res, s = [], [] # res用于保存返回结果 s是用来模拟递归过程的栈 也就是中序遍历的顺序
# 当 s 和 root 不同时为空时
while s or root:
# 如果当前节点不为空 则入栈 并将其左子树记为当前节点
if root:
s.append(root)
root = root.left
# 如果当前节点为空 则出栈(并保存当前值) 并将其右子树记为当前节点
else:
root = s.pop() # ps 出栈可以用s.pop
res.append(root.val)
root = root.right
return res
对于后序的思考1:
后序的遍历顺序为:左右根(7-4-5-2-6-3-1)
按照上述的思想以及仿照递归的规律,我们可能要添加根节点的位置遍历完右节点后添加,改写为如下,但是这么做其实是中序的写法。
temp = s.pop()
root = temp.right
res.append(temp.val)
后序难在要考虑根节点如何最后加到结果里。故在遍历的过程中,当左节点为空的时候,也需要将右节点放到栈中。
思想过程:
① res记录遍历结果, s 作为栈来模拟递归。
② 当root不为空或者s不空时执行③(代表仍有元素未遍历到)
③ 若root不为空:(第一遍s=[1 2 4 7])
将root加入到s;
当root.left不空时候加到s中 (即root=root.left)
当root.left为空时,将root.right加到s中(即root=root.right)
④ 若root空(即左右子树都为空 或者左右子树都遍历完),则将 新root pop出来,添加root值到res中(遍历根,第一个为7)。此时如果s不为空且s中最后一个元素(4)的左子树是root,更新root为root.right。否则,更新root为空。
代码:
def lateorderNoRecur2(root):
res, s = [], []
while s or root:
# 如果该节点不为空,则一直添加左子树 或 当左子树为空时候 添加右子树 (因为根节点是最后遍历的)
if root:
s.append(root)
root = root.left if root.left else root.right
# 如果该节点为空(对于s中最后一个节点来说 是左右都为空即左右子树都没有值),则将s最后一个节点pop出来记为root 添加到结果中并更新root:
# ① 如果s不空且当前的root为s最后一个节点的左子树 则新root赋值为s最后一个节点的右子树 ② 否则 代表root为s最后一个节点的右子树 新root需要赋值为none 代表s[-1]的两个子树均已遍历完毕
else:
root = s.pop()
res.append(root.val)
root = s[-1].right if s and s[-1].left == root else None
return res
对于后序的思考2:(讨巧的办法 利用前序)
后序的遍历顺序是:左右根
前序的遍历顺序的:根左右
所以可以先将前序进行对称,变为根右左,再做逆序,变为左右根。
def lateorderNoRecur1(root):
res, s = [], []
while root or s:
# 当前节点不为空
if root:
res.append(root.val) # 添加返回值位置 因为先遍历根部
s.append(root) # 入栈
root = root.right # 遍历右节点
# 如果当前节点为空
else:
root = s.pop() # 出栈
root = root.left # 遍历左节点
return res[::-1]
对于层次遍历的思考:(广度优先)
# 层序遍历 - 迭代
# ps:如何做到每一层存放到一个列表中 每一层的时候计算 队列的长度 进行循环
def levelOrder(root):
if not root:
return []
res, q = [], [root]
while q:
leversize = len(q)
tempres = [] # 记录每一层的结果
for _ in range(leversize):
root = q.pop(0) # 队列先进先出 pop第一个
tempres.append(root.val)
if root.left:
q.append(root.left)
if root.right:
q.append(root.right)
res.append(tempres)
return res
总结
树的递归算法
递归终止条件
遍历左子树
遍历右子树
(什么时候保存当前节点值根据是前中后序来判断)
树的迭代遍历算法(模板)
def muban(root):
res, s = [], []
while root or s:
# 当前节点不为空
if root:
s.append() # 入栈
root =
# 如果当前节点为空
else:
root = s.pop() # 出栈
root =
return res
具体内容前中后具体分析。(可以使用前中两种+后序讨巧的方法,比较好记忆)
路虽远,行则将至。事虽难,做则必成 。