本文参考labuladong算法笔记[二叉搜索树心法(特性篇) | labuladong 的算法笔记]
1、概述
首先,BST(binary search tree) 的特性大家应该都很熟悉了(详见基础知识章节的 二叉树基础):
1、对于 BST 的每一个节点 node
,左子树节点的值都比 node
的值要小,右子树节点的值都比 node
的值大。
2、对于 BST 的每一个节点 node
,它的左侧子树和右侧子树都是 BST。
二叉搜索树并不算复杂,但我觉得它可以算是数据结构领域的半壁江山,直接基于 BST 的数据结构有 AVL 树,红黑树等等,拥有了自平衡性质,可以提供 logN 级别的增删查改效率;还有 B+ 树,线段树等结构都是基于 BST 的思想来设计的。
从做算法题的角度来看 BST,除了它的定义,还有一个重要的性质:BST 的中序遍历结果是有序的(升序),反向中序遍历则是降序。
也就是说,如果输入一棵 BST,以下代码可以将 BST 中每个节点的值升序打印出来:
def traverse(root):
if not root:
return
traverse(root.left)
# 中序遍历代码位置
print(root.val)
traverse(root.right)
230. 寻找第 K 小的元素
给定一个二叉搜索树的根节点 root
,和一个整数 k
,请你设计一个算法查找其中第 k
小的元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1 输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3 输出:3
提示:
- 树中的节点数为
n
。 1 <= k <= n <= 104
0 <= Node.val <= 104
进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k
小的值,你将如何优化算法?
这个需求很常见吧,一个直接的思路就是升序排序,然后找第 k
个元素呗。BST 的中序遍历其实就是升序排序的结果,找第 k
个元素肯定不是什么难事。
【python】
class Solution:
'''
思路:
1、利用BST特性,中序遍历该root,用列表承接每个节点,直到列表长度为k则返回最后一个元素
'''
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# res用于承接遍历结果
self.res = []
self.traverse(root, k)
return self.res[k-1]
def traverse(self, node, k):
if not node:
return
self.traverse(node.left, k)
self.res.append(node.val)
if len(self.res) == k:
return
self.traverse(node.right, k)
538. 把二叉搜索树转换为累加树 | 力扣 | LeetCode |
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node
的新值等于原树中大于或等于 node.val
的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
注意:本题和 1038: . - 力扣(LeetCode) 相同
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
示例 2:
输入:root = [0,null,1] 输出:[1,null,1]
示例 3:
输入:root = [1,0,2] 输出:[3,3,2]
示例 4:
输入:root = [3,2,4,1] 输出:[7,9,4,10]
提示:
- 树中的节点数介于
0
和104
之间。 - 每个节点的值介于
-104
和104
之间。 - 树中的所有值 互不相同 。
- 给定的树为二叉搜索树。
题目应该不难理解,比如图中的节点 5,转化成累加树的话,比 5 大的节点有 6,7,8,加上 5 本身,所以累加树上这个节点的值应该是 5+6+7+8=26。
我们需要把 BST 转化成累加树,函数签名如下:
def convertBST(root: TreeNode) -> TreeNode:
【思路】
按照二叉树的通用思路,需要思考每个节点应该做什么,但是这道题上很难想到什么思路。
BST 的每个节点左小右大,这似乎是一个有用的信息,既然累加和是计算大于等于当前值的所有元素之和,那么每个节点都去计算右子树的和,不就行了吗?
这是不行的。对于一个节点来说,确实右子树都是比它大的元素,但问题是它的父节点也可能是比它大的元素呀?这个没法确定的,我们又没有触达父节点的指针,所以二叉树的通用思路在这里用不了。
此路不通,我们不妨换一个思路,还是利用 BST 的中序遍历特性。
刚才我们说了 BST 的中序遍历代码可以升序打印节点的值,那如果我想降序打印节点的值怎么办?
很简单,只要把递归顺序改一下,先遍历右子树,后遍历左子树就行了:
class Solution:
def __init__(self):
# 记录累加和
self.sum = 0
def convertBST(self, root):
self.traverse(root)
return root
def traverse(self, root):
if root is None:
return
self.traverse(root.right)
# 维护累加和
self.sum += root.val
# 将 BST 转化成累加树
root.val = self.sum
self.traverse(root.left)
这道题就解决了,核心还是 BST 的中序遍历特性,只不过我们修改了递归顺序,降序遍历 BST 的元素值,从而契合题目累加树的要求。
简单总结下吧,BST 相关的问题,要么利用 BST 左小右大的特性提升算法效率,要么利用中序遍历的特性满足题目的要求,也就这么些事儿吧。
2、总结
所谓BST的特性,关键就是:
1、BST本身特性,左孩子值 < 父节点值 < 右孩子值。
2、基于1,BST中序遍历(左中右)结果是升序排列节点值,反向中序遍历(右中左)则是降序排列。