二叉搜索树的递归定义:
- 是一棵空树
- 是一棵由根节点、左子树、右子树组成的树,同时左子树和右子树都是二叉搜索树,且左子树上所有节点的数据域都小于等于根节点的数据域,右子树上所有节点的数据域都大于等于根节点的数据域
满足以上两个条件之一的二叉树,就是二叉搜索树
从这个定义我们可以看出,二叉搜索树强调的是数据域的有序性。也就是说,二叉搜索树上的每一棵子树,都应该满足 左孩子 <= 根结点 <= 右孩子
这样的大小关系。
查找数据域为某一特定值的节点
假设这个目标节点点的数据域值为 n,我们借助二叉搜索树数据域的有序性,可以有以下查找思路:
- 递归遍历二叉树,若当前遍历到的结点为空,就意味着没找到目标结点,直接返回。
- 若当前遍历到的结点对应的数据域值刚好等于
n
,则查找成功,返回。 - 若当前遍历到的结点对应的数据域值大于目标值
n
,则应该在左子树里进一步查找,设置下一步的遍历范围为root.left
后,继续递归。 - 若当前遍历到的结点对应的数据域值小于目标值
n
,则应该在右子树里进一步查找,设置下一步的遍历范围为root.right
后,继续递归。function search(root, n) {
// 若 root 为空,查找失败,直接返回
if(!root) {
return
}
// 找到目标结点,输出结点对象
if(root.val === n) {
console.log('目标结点是:', root)
} else if(root.val > n) {
// 当前结点数据域大于n,向左查找
search(root.left, n)
} else {
// 当前结点数据域小于n,向右查找
search(root.right, n)
}
}
插入新节点
function insert(root, n) {
// 若 root 为空,说明当前是一个可以插入的空位
if(!root) {
// 用一个值为n的结点占据这个空位
root = new TreeNode(n)
return
}
// 查找成功,说明值为n的结点已经存在,不再重复创建,直接返回
if(root.val === n) {
return
} else if(root.val > n) {
// 当前结点数据域大于n,向左查找
insert(root.left, n)
} else {
// 当前结点数据域小于n,向右查找
insert(root.right, n)
}
}
删除指定节点
function delete(root, n) {
// 如果没找到目标结点,则直接返回
if(!root) {
return
}
// 定位到目标结点,开始分情况处理删除动作
if(root.val === n) {
// 若是叶子结点,则不需要想太多,直接删除
if(!root.left && !root.right) {
root = null
} else if(root.left) {
// 寻找左子树里值最大的结点
const maxLeft = findMax(root.left)
// 用这个 maxLeft 覆盖掉需要删除的当前结点
root.val = maxLeft.val
// 覆盖动作会消耗掉原有的 maxLeft 结点
delete(root.left, maxLeft.val)
} else {
// 寻找右子树里值最小的结点
const minRight = findMin(root.right)
// 用这个 minRight 覆盖掉需要删除的当前结点
root.val = minRight.val
// 覆盖动作会消耗掉原有的 minRight 结点
delete(root.right, minRight.val)
}
} else if(root.val > n) {
// 若当前结点的值比 n 大,则在左子树中继续寻找目标结点
delete(root.left, n)
} else {
// 若当前结点的值比 n 小,则在右子树中继续寻找目标结点
delete(root.right, n)
}
}
// 寻找左子树最大值
function findMax(root) {
while(root.right) {
root = root.right
}
return root
}
// 寻找右子树的最小值
function findMin(root) {
while(root.left) {
root = root.left
}
return root
}
二叉搜索树的验证
题目描述:给定一个二叉树,判断其是否是一个有效的二叉搜索树。 假设一个二叉搜索树具有如下特征: 节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。
示例 1: 输入:
2
/ \
1 3
输出: true
示例 2: 输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
/**
* @param {TreeNode} root
* @return {boolean}
*/
const isValidBST = function(root) {
// 定义递归函数
function dfs(root, minValue, maxValue) {
// 若是空树,则合法
if(!root) {
return true
}
// 若右孩子不大于根结点值,或者左孩子不小于根结点值,则不合法
if(root.val <= minValue || root.val >= maxValue) return false
// 左右子树必须都符合二叉搜索树的数据域大小关系
return dfs(root.left, minValue,root.val) && dfs(root.right, root.val, maxValue)
}
// 初始化最小值和最大值为极小或极大
return dfs(root, -Infinity, Infinity)
};
将排序数组转化为二叉搜索树
题目描述:将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例: 给定有序数组: [-10,-3,0,5,9], 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
/**
* @param {number[]} nums
* @return {TreeNode}
*/
const sortedArrayToBST = function(nums) {
// 处理边界条件
if(!nums.length) {
return null
}
// root 结点是递归“提”起数组的结果
const root = buildBST(0, nums.length-1)
// 定义二叉树构建函数,入参是子序列的索引范围
function buildBST(low, high) {
// 当 low > high 时,意味着当前范围的数字已经被递归处理完全了
if(low > high) {
return null
}
// 二分一下,取出当前子序列的中间元素
const mid = Math.floor(low + (high - low)/2)
// 将中间元素的值作为当前子树的根结点值
const cur = new TreeNode(nums[mid])
// 递归构建左子树,范围二分为[low,mid)
cur.left = buildBST(low,mid-1)
// 递归构建左子树,范围二分为为(mid,high]
cur.right = buildBST(mid+1, high)
// 返回当前结点
return cur
}
// 返回根结点
return root
};