二叉搜索树,是二叉树中最常用的一种类型。二叉搜索树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
二叉搜索树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。
题型一、二叉搜索树的递归
写递归的思路:
-
思考能否将问题分解为几个子问题来求解,如果可以那么递归解决。
-
明确递归函数的定义(返回值是什么、接收参数)。
二叉搜索树的查找
二叉搜索树的查找、插入操作只需将对象节点与根节点比较,根据比较的结果决定直接返回还是递归的查找左子树或右子树。
-
分解问题: search 根树 = return 根树 || search 左子树 || search 右子树
-
递归函数返回查找到的子树
var searchBST = function(root, val) {
if (!root) return null;
if (root.val === val) {
return root;
} else if (root.val > val) {
return searchBST(root.left, val);
} else {
return searchBST(root.right, val);
}
};
二叉搜索树的插入
-
分解问题:插入到左子树 || 插入到右子树
-
递归函数返回插入操作完成后的根节点
var insertIntoBST = function(root, val) {
if (!root) return new TreeNode(val);
if (root.val > val) {
root.left = insertIntoBST(root.left, val);
}
if (root.val < val) {
root.right = insertIntoBST(root.right, val);
}
return root;
};
二叉搜索树的删除
二叉搜索树的删除操作比较复杂,针对要删除节点的子节点个数的不同,需要分三种情况来处理:
- 如果要删除的节点只有一个子节点是末端节点,两个子节点都为空,那么它可以直接删掉
- 如果要删除的节点只有一个子节点,那么只要让它非空子孩子接替自己的位置即可
- 如果要删除的节点有两个子节点,需要找到这个节点的右子树中最小节点,把它替换到要删除的节点上。然后再删除这个最小节点即可(因为是最小节点,所以肯定没有子节点了)
var deleteNode = function(root, key) {
// 递归函数定义:删除某节点,并返回删除后的 BST
if (!root) return null;
const getMin = root => {
while (root.left) {
root = root.left;
}
return root;
}
if (root.val === key) {
if (!root.left) {
return root.right;
} else if (!root.right) {
return root.left;
} else {
let minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, minNode.val);
}
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else {
root.left = deleteNode(root.left, key);
}
return root;
};
98. 验证二叉搜索树
-
分解问题:validate 根树= validate root.val || validate 左子树 || validate 右子树
-
递归函数返回表示是否是二叉搜索树的 boolean 值
var isValidBST = function(root) {
const validate = (root, min = -Infinity, max = +Infinity) => {
if (!root) return true;
if (root.val >= max || root.val <= min) return false;
return validate(root.left, min, root.val) && validate(root.right, root.val, max);
}
return validate(root);
};
235. 二叉搜索树的最近公共祖先
-
分解问题:
- 最近公共祖先是根节点(p > root.val && q < root.val)
- 最近公共祖先在左子树中(p < root.val && q < root.val)
- 最近公共祖先在右子树中(p > root.val && q > root.val)
- 递归函数返回公共祖先节点
var lowestCommonAncestor = function(root, p, q) {
if (p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q);
} else if (p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q);
} else {
return root;
}
};
669. 修剪二叉搜索树
-
分解问题:
- root.val < low,只需修剪右边即可
- root.val > high,只需修剪左边即可
- low < root.val < high,那么左右子树都需要
-
递归函数返回修剪好的二叉树
var trimBST = function(root, low, high) {
//递归函数的定义:删除 BST 中小于 low 和大于 high 的所有节点,返回结果 BST
if (!root) return null;
if (root.val < low) {
return trimBST(root.right, low, high);
}
else if (root.val > high) {
return trimBST(root.left, low, high);
} else {
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
}
return root;
};
题型二、二叉搜索树的中序遍历
明确中序遍历二叉搜索树等于遍历一个有序数组即可,时间复杂度是 O(n)。
530. 二叉搜索树的最小绝对差
var getMinimumDifference = function(root) {
let min = Infinity;
let preVal = undefined;
const traversal = root => {
if (!root) return;
traversal(root.left);
// 不能写成 if (preVal), 因为 0 是假值, 会产生 bug
if (preVal !== undefined) {
min = Math.min(min, root.val - preVal);
}
preVal = root.val;
traversal(root.right);
}
traversal(root);
return min;
};
538. 把二叉搜索树转换为累加树
BST的中序遍历是从小到大,那么反过来就是从大到小,然后累加就好了。
var convertBST = function(root) {
let sum = 0;
let convertBST = (root) => {
if(!root) return;
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
convertBST(root);
return root;
};