定义:
二叉树的一种,专注于检索,特点是让节点按照一定的规律摆放,从而让搜索某个节点特别的高效;其中每个结点就是一个对象。除了结点中的关键字外,每个结点还包含属性left、right、p,它们分别指向结点的左孩子、右孩子和父亲结点。如果某个孩子结点和父结点不存在,则相应属性的值为null。根结点是树中唯一父结点为null的结点。
特点:
- 如果节点的左子树不空,则左子树上所有结点的值均小于等于它的根结点的值;
- 如果节点的右子树不空,则右子树上所有结点的值均大于等于它的根结点的值;
- 任意节点的左、右子树也分别为二叉搜索树
操作:
增:
根据二叉搜索树的定义,所以需要保证无论往这颗树上增加多少个节点,都需要保证其定义不被破坏。当遇到一个新的节点需要插入时,我们可以与根节点进行比较,如果比根节点值大就放入右子树内,如果比根节点值小就放入左子树内,使用这样的插入规则,逐层往下,总会遇到一个节点的孩子为空,将新节点设置为其新的孩子节点即可
class TreeNode {
constructor(val) {
this.val = val
this.left = null // 左孩子
this.right = null // 右孩子
}
}
class BST {
constructor() {
this.root = null // 根节点
}
}
class BST {
...
add(val) { // 不考虑重复值的情况
const _helper = (node, val) => {
if (node === null) { // 如果某个节点的孩子节点为空,创建新节点返回即可
return new TreeNode(val)
}
if (node.val > val) { // 如果当前节点值比val大,新节点需要放在其左子树里
node.left = _helper(node.left, val) // 以当前节点左孩子为起点,返回新的左孩子节点
return node // 返回新的节点
} else if (node.val < val) { // 同理
node.right = _helper(node.right, val) // 同上
return node
}
}
this.root = _helper(this.root, val)
// 从根节点开始插入val, 返回新的根节点
}
}
查:
根据二叉搜索树的特性从根节点开始比较,还是逐层进行递归,如果根据规则最后遇到了空节点,那说明这颗树里没有这个节点。
class BST {
...
contains(val) {
const _helper = (node, val) => {
if (node === null) { // 如果到头也没右找到
return false
}
if (node.val === val) { // 正好找到
return true
}
if (node.val > val) { // 如果比当前节点小
return _helper(node.left, val) // 去左子树里找
} else { // 如果比当前节点大
return _helper(node.right, val) // 去右子树里找
}
}
return _helper(this.root, val) // 从根节点开始
}
}
删:
二叉搜索树最麻烦的操作也就是删除,因为在删除了一个节点之后,原来位置的节点并不会空着,必须找到替代的节点续上才行。怎么续上而又保持二叉搜索树的定义又分为几种情况:
1.一边有孩子节点:
场景1里面的两种场景,将另一边的孩子节点续上即可。
2.两边都有孩子节点:
场景2里面也有两种处理方式,一种是在待删除节点的左子树里面找到最大的值替代(24),然后切断其链接;另一种是在待删除的右子树里面找到最小值替代(28),然后切断其链接。
为了应对场景2,我们需要找到指定节点树的最小或最大节点,然后还需要在指定节点树里删除最小或最大的节点,所以先把辅助方法写出来以便后面使用,这里采用在右子树里找最小值的方案:
class BST {
...
_minimum(node) { // 返回指定节点树的最小值节点
if (node.left === null) {
return node
}
return this._minimum(node.left)
}
_removeMin(node) { // 删除指定节点树的最小节点
if (node.left === null) {
const rightNode = node.right
node.right = null
return rightNode
}
node.left = this._removeMin(node.left)
return node
}
}
根据二叉搜索树的特性,指定树的最小值一定就是沿着左边一直找,最后找到的那个非空节点;而删除指定树的最小值节点,也只需要用被删除节点的右子树续上即可。
class BST {
...
remove(val) { // 删除指定值
const _helper = (node, val) => {
if (node === null) { // 如果到底了也没有
return node // 直接返回空
}
if (node.val === val) { // 如果正好找到了
if (node.left === null) { // 左孩子为空时,让右孩子续上
const rightNode = node.right
node.right = null
return rightNode
} else if (node.right === null) { // 右孩子为空时,让左孩子续上
const leftNode = node.left
node.left = null
return leftNode
} else { // 如果左右都右孩子
const successor = this._minimum(node.right) // 在右子树里找最小的节点
node.right = this._removeMin(node.right) // 并且删除右子树里最小的节点
successor.left = node.left // 让新节点指上之前的左孩子
successor.right = node.right // 让新节点指上之前的右孩子
return successor // 返回新节点
}
} else if (node.val > val) { // 比值递归
node.left = _helper(node.left, val)
return node
} else { // 比值递归即可
node.right = _helper(node.right, val)
return node
}
}
this.root = _helper(this.root, val) // 从根节点开始,删除指定值没返回值
}
}
中序遍历:
二叉搜索树的中序遍历是一种遍历方式,它按照从小到大的顺序遍历树中的节点,因为中序遍历会首先访问左子树、然后访问根节点、最后访问右子树。以下是中序遍历的递归和非递归实现方法:
递归实现中序遍历:
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function inOrderTraversal(node) {
if (node !== null) {
inOrderTraversal(node.left); // 遍历左子树
console.log(node.value); // 访问当前节点
inOrderTraversal(node.right); // 遍历右子树
}
}
// 使用示例
const root = new TreeNode(5);
root.left = new TreeNode(3);
root.right = new TreeNode(8);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(4);
root.right.left = new TreeNode(7);
root.right.right = new TreeNode(9);
inOrderTraversal(root); // 输出:1, 3, 4, 5, 7, 8, 9
非递归实现中序遍历(使用栈):
function inOrderTraversal(root) {
const stack = [];
const result = [];
let current = root;
while (current !== null || stack.length > 0) {
while (current !== null) {
stack.push(current);
current = current.left;
}
current = stack.pop();
result.push(current.value);
current = current.right;
}
return result;
}
// 使用示例同上
const result = inOrderTraversal(root);
console.log(result); // 输出:[1, 3, 4, 5, 7, 8, 9]