二叉树写递归,这样写才快准狠 2019-11-25(未经允许,禁止转载)

由于树本身是递归定义的特性,树问题往往都可以通过写递归来解决。但是如何写对和写好这个递归,是比较困难的一点

之前写过一篇博客介绍自己写递归的方式,点这里
递归要搞清楚2件事,函数做什么事(分段函数做事)+ 函数返回什么值
现在就二叉树问题再具体地看一下

引入

先从简单的引入,前序遍历二叉树相信大家都非常熟悉了

def preOrder(Tree node):
    if node == None:
        return
    visit(node.data)
    preOrder(node.leftchild)
    preOrder(node.rightchild)

在这里,函数做事情的分段函数就是
# 出口段
if node == None:
return
# 继续段
visit(node.data)
preOrder(node.leftchild)
preOrder(node.rightchild)

其实,二叉树的递归出口一般都比较容易写,一般都是 node == None 或者 具体问题下某些特定终止的情况

难就难在继续段要做什么事,怎么写代码。所有二叉树问题递归形式的继续段有没有什么共通的特点呢?

有的

金道理1:要从树的根节点root开始,通过不断递归实现对整棵树的有规律的访问,就必须对root的所有孩子结点做递归对于二叉树而言,在root结点对应的函数体中,必须包含对root.left和root.right的递归,才能保证对整棵树进行完整的递归访问

如先序遍历中的preOrder(node.leftchild)preOrder(node.rightchild)

以上是得出的写二叉树递归的共性结论
下面用一些具体问题巩固理解

深入

1. 给定一个二叉树,找出其最大深度

思路

  • 确定函数返回值:
    返回以传入的参数结点root为根结点的树的深度

  • 找分段函数:

    • 出口段:显然,当传入的参数结点root为None时,直接返回0
    • 继续段:递归访问左孩子结点,可得到左子树的深度;递归访问右孩子结点,可得到右子树的深度;返回 1 + max(左深,右深)
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))

2. 给定一个非空二叉树,返回其最大路径和

示例:


输出: 42(15+20+7)

思路

  • 确定函数返回值:
    返回以传入参数结点root为根结点的最长一支(左支或者右支)的长度和,而不是以传入的参数结点root为根结点的树的最大路径和,即不直接返回题目的所求
    思考:为什么这里不能像上题那样让函数直接返回题目所求呢?什么情况下可以直接返回所求,什么情况下不可以呢?

  • 找分段函数:

    • 出口段:显然,和上题一样,当传入的参数结点root为None时,直接返回0
    • 继续段:
      • 递归访问左孩子结点,可得到以左孩子为根结点的最长一支的长度和max_left;
      • 递归访问右孩子结点,可得到以右孩子为根结点的最长一支的长度和max_right;
      • 这时,将树的最长路径和max_len 修改为 (root.val + max_left + max_right, root.val + max_left, root.val + max_right, root.val, max_len)中最大的一个;
      • 最后 return max(root.val + max_left, root.val + max_right, root.val),作为以传入的参数结点root为根结点的最长一支的长度和
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def __init__(self):
        self.max_len = 0
    
    def maxPathSum(self, root: TreeNode) -> int:
        # 先给self.max_len赋有意义的值
        if root:
            self.max_len = root.val
        self.searchMaxBranch(root)
        return self.max_len
    
    # 这里是核心代码
    # 返回经过传入的参数结点root的左右支的最大一支的路径 and 修改树的最大路径长度
    def searchMaxBranch(self, root) -> int:
        if not root:
            return 0
        max_left = self.searchMaxBranch(root.left)
        max_right = self.searchMaxBranch(root.right)
        # 修改self.max_len
        self.max_len = max(root.val + max_left + max_right, root.val + max_left, root.val + max_right, root.val, self.max_len)
        # 返回经过传入的参数结点root的左右支的最大一支的路径
        return max(root.val + max_left, root.val + max_right, root.val)

现在我们回到刚才提出的思考,为什么这里不能让定义的函数直接返回所求呢?什么情况下可以直接返回所求,什么情况下不可以呢?

金道理2:事实上,通过观察两题的区别可以发现,求树的深度所形成的结果路径是纵向路径,不会经过某个结点的两支;而求树的最长路径所形成的结果路径则会经过某个结点的两支,属于横向路径,如示例中的结果 42 = 15+20+7,15+20+7这条路径经过了结点20的两支。我们知道,树的递归遍历的特点是深度优先,因此树的递归一定只能产生纵向路径,当我们遇到横向路径时,要先等价转换为两条纵向路径之和

弄懂金道理1 & 金道理2,二叉树写递归,稳了

你可能感兴趣的:(二叉树写递归,这样写才快准狠 2019-11-25(未经允许,禁止转载))