由于树本身是递归定义的特性,树问题往往都可以通过写递归来解决。但是如何写对和写好这个递归,是比较困难的一点
之前写过一篇博客介绍自己写递归的方式,点这里
递归要搞清楚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,二叉树写递归,稳了
网友评论