先序和中序遍历序列确定一颗二叉树(还原二叉树)

我用了两种方法解,分别是递归法和堆栈法。递归法是最简单的,学完递归法后,建议一定要学习堆栈法,因为递归程序实际上也是堆栈实现的。

    分析:        

  • 先根据线序遍历序列确定根结点,即第一个结点(根左右)。
  • 根据根节点再中序遍历序列中分别分割出左子树和右子树(左根右:根节点左侧为左子树,右侧为右子树)。
  • 对左子树和右子树分别递归使用相同的方法继续分解

    例:

        先序序列  a bcde fghij ;

        中序序列 cbed a hgijf

        对于根节点a,先序、中序序列可以分别分解为下图两棵树:

          对于每个节点的左子树、右子树,按照同样的算法逐步分解,最后可以确认一棵唯一树。

一、递归法       

递归代码实现如下(Python3): 每次递归都能根据先序、中序序列确认一棵唯一树。

def buildTree(preorder:str, inorder:str):
    """
    根据前序,中序,确认唯一树
    preorder: 先序序列
    inorder: 中序序列
    """
    if not preorder:
        return None
    
    # 构造当前序列“根”节点
    p = T = BinTreeNode()  
    p.value = preorder[0]
    index = inorder.find(p.value)
    if index == -1:
        print("非法树")
    l_in = inorder[:index]  # 中序序列左子树
    r_in = inorder[index+1:]  # 中序序列右子树
    l_pre = preorder[1:len(l_in)+1]  # 先序序列左子树
    r_pre = preorder[len(l_in)+1:]  # 先序序列右子树

    # 构造当前序列左、右子树
    p.left = buildTree(l_pre, l_in)  
    p.right = buildTree(r_pre, r_in)

    return T



T = buildTree("abcdefghij", "cbedahgijf")

二、堆栈法

        实现递归算法后,虽然简单,而且代码逻辑清晰,但是效率太低了。我们知道,递归实际上就是利用栈实现的,那么我们同样可以利用这个原理来做。

        分析:       

  1.   首先一直构造树的最左子树,直到没有左子树,每遇到一个结点就放进栈顶;
  2.   返回上一个结点(从栈顶弹出一个元素),构造其右子树;
  3.   返回第一步,直到栈为空,所有结点构造完毕。

说明:为什么是栈呢?因为构造完左子树,我们需要回退到上一步构造右子树,即知道当前结点的父亲是谁,或者知道当前结点的右兄弟是谁,栈的后进先出性质就很适合用来保存“上一步”。递归也是利用这个原理。

       

具体步骤图解如下:

        第一步:构造左子树,直到左子树为空(^),此时栈内有【a , b, c

         第二步:回到上一步(c),构造其右子树,右子树为空,弹出栈顶元素(b)。

        第三步:当前结点是b,继续构造右子树。

        第四步:回到第一步,构造当前结点(d)的左子树,直到左子树为空。如此循环,当根节点左子树构造完成后,状态如下:

        右子树同理。

代码实现:

def buildTree3(preorder:str, inorder:str):
    """
    根据前序,中序,确认唯一树
    """
    if not preorder:
        return None

    # 树根
    p = T = BinTreeNode()
    s = ArrStack()

    p.value = preorder[0]

    preo = preorder[1: inorder.find(p.value)+1]
    inord = inorder[: inorder.find(p.value)]
    
    # 根结点、右子树序列入栈
    s.push((p, preorder[inorder.find(p.value)+1:], inorder[inorder.find(p.value)+1:]))  
    
    while(p or (not s.isEmpty())):
        if (preo):  # 左子树序列不为空,一直构造左子树
            t = BinTreeNode()
            t.value = preo[0]
            p.left = t
            p = p.left

        else:
            # 没有左子树了, 弹出一个栈顶结点, 转到右子树
            p, preo, inord = s.pop()
            if not preo:  # 右子树序列为空,跳过
                p = p.right
                continue

            # 右子树序列不为空,继续构造
            t = BinTreeNode()
            t.value = preo[0]
            p.right = t
            p = p.right

        # 分别找到左右子树
        i = inord.find(preo[0])
        lino = inord[:i]  # 中序遍历左子树
        rino = inord[i + 1:]  # 中序遍历右子树
        lpreo = preo[1:len(lino) + 1]  # 先序遍历左子树
        rpreo = preo[len(lino) + 1:]  # 先序遍历右子树

        # 下一层左子树
        preo = lpreo
        inord = lino

        # 记住自己,以及两个序列的右子树
        if p:
            s.push((p, rpreo, rino))  # 记住自己或者记住右子树

    return T

三、效率对比

调用 

 效率比大概是5-6倍。

四、总结

        通过这个练习,可以充分锻炼到递归、堆栈的思想。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值