- 二叉排序树也可以叫二叉搜索树
性质:
- 左子树所有节点值均小于根节点
- 右子树所有节点值均大于根节点
衍生性质:
-
基于二叉排序数的如上性质可知,在该树的中序遍历结果中,任意一个根节点前面的节点值均小于当前根节点值,其后面的节点值均大于当前节点值,所以中序遍历的结果为升序排列的
操作:
-
插入节点操作
- 从树的根节点开始进行比较
- 若新节点值较小,则递归进入左子树中进行插入操作
- 若新节点值较大,则递归进入右子树中进行插入操作
- 找到合适的叶子节点后,再根据叶子节点值与新节点值的大小关系,将新节点插入到该叶子节点的左右子节点中
-
删除节点操作
- 删除度为 0 的节点,也就是叶子节点
- 直接删除即可
- 删除度为 1 的节点
- 需要将被删除节点的子节点过继给其父节点,相当于让子节点替换被删除节点的位置
- 删除度为 2 的节点
- 需要找到被删除节点的前驱节点或者后继节点
- 前驱或者后继指的是在中序遍历的结果中,与该节点相邻的前一个节点或者后一个节点
- 也就是左子树中最右边的节点,或者右子树中最左边的节点
- 将被删除节点与前驱节点或者后继节点进行替换
- 再删除被替换掉的前驱或者后继即可
- 需要找到被删除节点的前驱节点或者后继节点
- 删除度为 0 的节点,也就是叶子节点
代码示例
- insert
- 此方法返回插入节点之后的,这棵树的根节点
- 如果插入节点的值 key 已存在于树中,则不插入
- 左子树插入节点后的子树根节点赋值给 root.lefts, 右子树相同
- erase
- 此方法返回删除节点之后的,这棵树的根节点
- 先根据当前节点值与需要删除的 key 值进行比较,定位删除节点在左子树还是右子树中
- 再按照删除节点度的数量,进行对应的删除操作
- getPredeccessor
- 获取前驱,也就是当前节点的左子树中最右边的叶子节点
- output
- 打印二叉排序树的中序遍历结果,正常情况下一定是升序排列的
// 节点
class Node {
constructor(key, left = null, right = null) {
this.key = key;
this.left = left;
this.right = right;
}
}
const getNewNode = (key) => new Node(key);
// 销毁整棵树
const clear = (root) => {
if (!root) return;
clear(root.left);
clear(root.right);
root = null;
};
// 插入节点
const insert = (root, key) => {
if (!root) return getNewNode(key);
if (root.key === key) return root;
if (key < root.key) root.left = insert(root.left, key);
else root.right = insert(root.right, key);
return root; // 返回插完节点后的树的根节点
};
// 获取前驱
const getPredeccessor = (root) => {
let p = root.left;
while (p.right) p = p.right;
return p;
};
// 删除节点
const erase = (root, key) => {
if (!root) return root;
if (key < root.key) root.left = erase(root.left, key);
else if (key > root.key) root.right = erase(root.right, key);
else {
// 此时当前节点值与 key 值相等,需要被删除
if (!root.left && !root.right) {
// 若度为 0 则可直接删除
return (root = null);
} else if (!root.left || !root.right) {
// 若度为 1 则需要将其子节点过继给其父节点
return !root.left ? root.right : root.left;;
} else {
// 此时度为 2 则找到前驱或者后继进行替换,然后删除前驱或者后继即可
const predeccessor = getPredeccessor(root);
root.key = predeccessor.key;
root.left = erase(root.left, predeccessor.key);
}
}
return root;
};
const output = (root, list = []) => {
if (!root) return;
output(root.left, list);
list.push(root.key);
output(root.right, list);
return list.join(' ');
};
let tree = null;
tree = insert(tree, 3);
console.log(output(tree)); // 3
tree = insert(tree, 1);
console.log(output(tree)); // 1 3
tree = insert(tree, 1);
console.log(output(tree)); // 1 3
tree = insert(tree, 4);
console.log(output(tree)); // 1 3 4
tree = insert(tree, 5);
console.log(output(tree)); // 1 3 4 5
tree = insert(tree, 6);
console.log(output(tree)); // 1 3 4 5 6
erase(tree, 3);
console.log(output(tree)); // 1 4 5 6
erase(tree, 4);
console.log(output(tree)); // 1 5 6
小结
- 本文介绍了二叉排序树的基本性质与基本操作,如果有收获的话,请点个赞吧~
- 下篇文章分享 AVL 树的相关性质与操作
“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”