《剑指offer》06&07、二叉树的重构与下一个结点

二叉树基础

关于二叉树的基础结构,请参考
《剑指offer》预备知识-链表与二叉树
今天我们以如下这棵二叉树作为例子。

重构二叉树

offer06的要求是,给出一棵二叉树的前序遍历和中序遍历,要求重构这棵二叉树,那我们就需要知道什么叫前序遍历和中序遍历(顺便说说后续遍历)。

前序遍历

对于每一个结点按照根结点→左子结点→右子结点的方式遍历
在本树中,先考察根结点6,它有左结点3和右结点7
对于左结点3,它作为根结点的话,有左结点1和右结点5
对于左结点1,它作为根结点的话,有右结点2作为叶结点。所以有6→3→1→2
回到左结点3的右结点5,它作为根结点的话有左结点4,所以有5→4
所以6的左子树的前序遍历为6→3→1→2→5→4
同理右子树的遍历为7→9→8→10,整个二叉树的前序遍历为6→3→1→2→5→4→7→9→8→10

中序遍历

对于每一个结点,按照左子结点→根结点→右子结点的方式遍历
在本树中,先考察根结点6,它有左结点3,
对于左结点3,它有左结点1
对于左结点1,它没有左子树了,左→根→右,它有右结点2作为叶结点,所以1→2
回到左结点3,左子树遍历完成,考察右结点5,它有左结点4,因此有4→5,中间以3连接,即:1→2→3→4→5
回到根结点6,左子树遍历完成,同理遍历右子树,有7→8→9→10,最终合在一起就是:
1→2→3→4→5→6→7→8→9→10

后序遍历

对于每一个结点,按照左子结点→右子结点→根结点的方式遍历
在本树中,先考察根结点6,它有左结点3
对于左结点3,它有左结点1,
对于左结点1,它有右结点2作为叶结点所以有2→1
回到左结点3,左子树遍历完成,考察右结点5,它有左结点4,因此有4→5,连在一起,最后才遍历3,这里是2→1→4→5→3,
回到根结点6,左子树遍历完成,同理遍历右子树:8→10→9→7,最后遍历根结点6,合在一起是:
2→1→4→5→3→8→10→9→7→6

从前中序遍历中找规律

现在我们不看图,我们知道一棵二叉树的前序遍历:
6→3→1→2→5→4→7→9→8→10
中序遍历:
1→2→3→4→5→6→7→8→9→10
如何人肉地把二叉树构建出来呢?
我们知道前序遍历按照根结点→左子结点→右子结点的方式遍历,因此前序遍历的第一个结点:6就是根结点。而中序遍历按照左子结点→根结点→右子结点的方式遍历,因此中序遍历中,6左边的是6的左子树,6右边的是6的右子树。
继续看前序遍历的3,它是6的左子树的根结点,和上述同理,我们从中序遍历中可以得知,1和2是3的左子树,4和5是3的右子树。
前序遍历和中序遍历都是1→2,意味着2是1的右结点。与此相反的,前序遍历给出了5→4,中序遍历给出了4→5,意味着4是5的左结点。至此根结点6的左子树重构完成。
那么右子树也是同理了,容易得到9是7的右结点,8和10是9的左右结点。

代码

回顾整个过程——先序遍历的第一个结点肯定是根结点,然后在中序遍历中找到此根结点,结点以左是二叉树的左子树,根结点以右是二叉树的右子树。然后,左右子女树又可以单独的看成一个独立的二叉树,然后再对其划分,如此递归

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

# offer06-solution
    def reConstructBinaryTree(self, pre, tin):
        if not pre or not tin:
            return None
        root = TreeNode(pre[0])
        val = tin.index(pre[0])

        root.left = self.reConstructBinaryTree(pre[1:val + 1], tin[:val])
        root.right = self.reConstructBinaryTree(pre[val + 1:], tin[val + 1:])
        return root

二叉树的下一个结点

offer07给出了一棵二叉树,要求它某个结点的中序遍历中的下一个结点。还是以刚才的二叉树为例:
1→2→3→4→5→6→7→8→9→10;要求给1输出2,给5输出6
由前面的知识,我们分情况讨论:
1、如果这个结点有右子树,那么下一个结点为右子树的最左结点(递归/while)
比如结点6,它的下一个结点7没有左子树,所以6的下一个结点就是7
而结点7,它的下一个结点9有左结点8,左结点8是叶结点,所以7的下一个结点是8
2、如果这个结点没有右子树,那么
2.1、如果这个结点是它的父结点的左孩子,那么该结点的下一个结点就是其父结点
比如4和8
2.2、如果这个结点是它的父结点的右孩子,那么就要一直向上追溯父结点的父结点,的父结点……直到某个父结点是其父结点的左孩子。
比如5,就要向上回溯两层到6.
根据这个规律,题解也呼之欲出了。

class TreeLinkNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
        self.next = None  # next是父结点
        
 # offer07-solution
    def GetNext(self, pNode):
        if not pNode:
            return
        # 如果该结点有右子树,那么下一个结点就是它右子树中的最左结点
        elif pNode.right != None:
            pNode = pNode.right
            while pNode.left != None:
                pNode = pNode.left
            return pNode
        # 如果一个结点没有右子树,并且它还是它父结点的右子结点
        elif pNode.next != None and pNode.next.right == pNode:
            while pNode.next != None and pNode.next.left != pNode:
                pNode = pNode.next
            return pNode.next
        # 如果一个结点是它父结点的左子结点,那么直接返回它的父结点
        else:
            return pNode.next

小结

这两题主要考察三序遍历的基础知识是否扎实。
参考:
二叉树的下一个结点(思路与实现)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值