树的四种遍历的递归和非递归方法(LC144、LC94、LC145、LC102)(hot100)

树的四种遍历的递归和非递归方法(LC144LC94LC145LC102)(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

具体内容前中后具体分析。(可以使用前中两种+后序讨巧的方法,比较好记忆)

 

 

路虽远,行则将至。事虽难,做则必成 。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值