《剑指offer》28-30、二叉树四则:之字形遍历、层次遍历、搜索二叉树的后序遍历、二叉树的路径

二叉树基础

关于二叉树的问题,请参考二叉树的预备知识,以及剑指offer之前的四道二叉树的题目。
《剑指offer》预备知识-链表与二叉树
《剑指offer》06&07、二叉树的重构与下一个结点
《剑指offer》23&24、树的子结构与镜像二叉树

层次遍历与之字形遍历

层次遍历

offer28其实提了两个要求,第一个是单纯的层次遍历,第二个则是之字形,即从左到右到左到右的层次遍历。先从简单的开始:
关于层次遍历是什么就不展开了……都到这份上了。以人的思维来看层次遍历貌似比前、中、后序遍历更加简单易懂,但是如果要化成程序语言,反而会变得复杂。在前、中、后序遍历中我们都考虑了栈,因此这里我们也需要用到一个数据结构:队列。
在脑海中思考一下层次遍历:在遍历完第一层的同时,我们需要从左往右记录下第二层的节点;然后遍历第二层时,再从左往右记录第三层的节点——所以核心就是,先把根节点加入队列,然后每次出队一个元素,并将该元素的孩子节点加入队列中,直到所有元素出队完毕
根据这个思路我们就可以构建代码了:

# offer28-solution
def PrintFromTopToBottom(self, root):
    if not root:
        return []
    res = []
    res_val = []
    res.append(root)  # 访问根节点
    while len(res) > 0:
        node = res.pop(0)  # 弹出根节点
        res_val.append(node.val)  # 向val中增加根节点的值
        if node.left:
            res.append(node.left)  # 向res中增加左右节点,维持res的长度,直到遍历到底层
        if node.right:
            res.append(node.right)
    return res_val

之字形层次遍历

之字形层次遍历二叉树的要求是:层次遍历二叉树,并且奇数行为从左到右(根节点为第一层),偶数行为从右到左。
既然本质还是层次遍历,那我们就加一个判断层数的指针,然后再活用python里的reverse()函数。

# offer28.1-solution
def PrintTree(self, pRoot):
    if not pRoot:
        return []
    res = []
    nodes = [pRoot]
    leftToRight = True

    while nodes:
        curStack, nextStack = [], []
        for node in nodes:
            curStack.append(node.val)
            if node.left:
                nextStack.append(node.left)
            if node.right:
                nextStack.append(node.right)
        if not leftToRight:
            curStack.reverse()
        res.append(curStack)
        leftToRight = not leftToRight
        nodes = nextStack

搜索二叉树的后序遍历

offer29的要求是:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出True,否则输出False。
首先有一个前提就是:假设输入的数组的任意两个数字都互不相同。然后是搜索二叉树的概念:左子结点值小于根节点值,根节点值小于右节点值,所以最后一个元素肯定是根节点。因此,该数列根节点前面的前一段肯定是小于该根节点值,根节点前的后一段是大于该根节点值。
我们举一个具体的例子:
数列:3 6 4 10 15 13 7
先看末尾的根节点7,它前面可以分为3 6 4和10 15 13一段比它大一段比它小的。
再分别看3 6 4的4,10 15 13的13,他们前面的两个数亦是一个比它们大,一个比它们小,所以这是一个搜索二叉树的后序遍历。
稍加修改也可以:
数列:3 4 6 10 15 13 7
这里的问题在于3 4 6,但实际上可以视为3和4在6的左子树,右子树为空;再进一步,3也是4的左子树,右子树为空。也是OK的
但下面这个就是反例了
数列:8 4 6 10 15 13 7
显然问题出在8 4 6上,8和4无论如何排列都不能满足二叉搜索树的特性,所以不行。

依照这个思路,我们可以写出代码。我当初写的代码莫名冗长,在文末给出了若干更简洁的版本,我们一起学习。

# offer29-solution
class Solution:
    def VerifySquenceOfBST(self, sequence):
        if not sequence or len(sequence) <= 0:
            return False
        root = sequence[-1]
        i = 0

        # 找出左小右大的分界点i,此时i属于右子树
        for node in sequence[:-1]:
            if node > root:
                break
            i += 1

        # 如果在右子树中有比根节点小的值,直接返回False
        for node in sequence[i:-1]:
            if node < root:
                return False
        # 判断左子树是否为二叉搜索树
        left = True
        if i > 0:  # 左子树有节点
            left = self.VerifySquenceOfBST(sequence[:i])
        # 判断右子树是否为二叉搜索树
        right = True
        if i < len(sequence) - 1:  # 尚未到达右子树的最后节点
            right = self.VerifySquenceOfBST(sequence[i:-1])

        return left and right

二叉树的路径

offer30的要求是:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径
感觉代码越写越臭,思路也越来越干,不过这算是二叉树中难度中等偏上的问题了。觉得纠结也是正常的。
依然先捋一捋思路,这种问题无法避免要涉及递归,我自己因为没有写DFS所以写得很臭,如果能合理运用DFS的话会简单很多。
首先我们的搜索方式必然是从根节点出发找叶子。找到叶子之后,所有这条“找寻之路”上的所有节点构成了我们要打印出来的一条路径。这里就需要两个全局变量:一个全局变量path存储未到达当前节点时扫描过的路径中有哪些节点。一个全局变量的结果列表作为我们的输出。
然后,我们从根节点开始,之后访问其左子树,再访问其右子树。每访问一个节点,将节点的值加入path,并将这里的path作为新的变量加入左右子树的遍历函数。
最后是递归的停止条件:访问的节点为空
请大家看代码和参考的文献

# offer30-solution
class Solution:
    # 返回二维列表,内部每个列表表示找到的路径
    def FindPath(self, root, expectNumber):
        if not root:
            return []
        result = []

        def FindPathCore(root, path, currentNum):  # 嵌套定义
            currentNum += root.val  # 当前节点的加和
            path.append(root)  # 加入路径
            # 判断是否达到叶子节点
            flag = (root.left == None and root.right == None)
            # 如果到达叶子节点且当前值等于期望值
            if currentNum == expectNumber and flag:
                onepath = []  # 为了递归方便,新开onepath,这也是因为可能有多个路径
                for node in path:
                    onepath.append(node.val)
                result.append(onepath)  # 弹出路径,result是二维数组

            if currentNum < expectNumber:  # 小于,递归
                if root.left:
                    FindPathCore(root.left, path, currentNum)
                if root.right:
                    FindPathCore(root.right, path, currentNum)
            path.pop()

        FindPathCore(root, [], 0)  # 一开始先input 0,空路径
        return result

小结与参考

二叉树作为一种基础的数据结构,可以考得很深入,其中的重点和难点就是递归甚至DFS的构建,以及一些基础技巧的掌握。参考文献同时给出了一些关于遍历和搜索相关的内容,供大家交流探讨。笔者认为下面的内容在或在思路,或在画图,或在代码上都做得比自己要好得多,多学习才能有进步。
后面的剑指offer好像剩下很少二叉树的题目了,但如果真正去面试的话,二叉树真的就,很关键。

二叉树搜索的后序遍历序列
二叉树搜索的后序遍历序列
leetcode257-二叉树的所有路径
二叉树的所有路径
先序、中序、后序遍历二叉树及二叉搜索树的总结
详解二叉树的遍历以及完全二叉树等6种二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值