二叉查找树(Binary Search Tree)
二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。
二叉查找树的特殊结构
二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值(如下图所示)。
1. 二叉查找树的查找操作
先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。
2. 二叉查找树的插入操作
二叉查找树的插入过程有点类似查找操作。新插入的数据一般都是在叶子节点上,所以我们只需要从根节点开始,依次比较要插入的数据和节点的大小关系。
如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。
3. 二叉查找树的删除操作
二叉查找树的查找、插入操作都比较简单易懂,但是它的删除操作就比较复杂了 。针对要删除节点的子节点个数的不同,我们需要分三种情况来处理。
第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。比如图中的删除节点 55。
第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。比如图中的删除节点 13。
第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。比如图中的删除节点 18。
代码demo
// 节点
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
// 二叉搜索树
class BinarySearchTree {
constructor() {
this.root = null;
this.count = 0;
}
// 向树中插入一个新的键
insert(key) {
if (this.root === null) {
this.root = new Node(key);
} else {
this.insertNode(this.root, key);
}
this.count += 1;
}
insertNode(node, key) {
if (key < node.key) {
if (node.left === null) {
node.left = new Node(key);
} else {
this.insertNode(node.left, key);
}
} else {
if (node.right === null) {
node.right = new Node(key);
} else {
this.insertNode(node.right, key);
}
}
}
// 在树中查找一个键。如果节点存在,则返回true;如果不存在,则返回false
search(key){
let bool = false;
let node = this.root;
while(node != null){
if(key === node.key){
bool = true;
break;
}
else if(key < node.key){
node = node.left;
}else{
node = node.right
}
}
return bool;
}
// 从树中删除一个key
remove(key) {
// node 指向要删除的节点,初始化指向根节点
let node = this.root;
// pNode 记录的是node的父节点
let pNode = null;
while (node !== null && node.key !== key) {
pNode = node;
if (key < node.key) {
node = node.left;
} else {
node = node.right;
}
}
// 没有找到
if (node === null) {
return;
}
// 第1种:删除的节点左节点和右节点都没有
if (node.left === null && node.right === null) {
if (key < pNode.key) {
pNode.left = null;
} else {
pNode.right = null;
}
} else if (node.left === null || node.right === null) { // 第2种:删除的节点有左节点或者右节点一个节点
const child = node.left || node.right;
if (key < pNode.key) {
pNode.left = child;
} else {
pNode.right = child;
}
} else { //第3种:删除的节点左节点和右节点都有
let minNode = node.right;
let pMinNode = node;
while (minNode.left !== null) {
pMinNode = minNode;
minNode = minNode.left;
}
// 将minNode的数据替换到node中
node.key = minNode.key;
// 删除minNode
pMinNode.left = minNode.right;
}
this.count -= 1;
}
// 前序遍历
preOrder(callback) {
this.preOrderNode(this.root, callback);
}
preOrderNode(node, callback) {
if (node !== null) {
callback(node.key);
this.preOrderNode(node.left, callback);
this.preOrderNode(node.right, callback);
}
}
// 中序遍历
inOrder(callback) {
this.inOrderNode(this.root, callback);
}
inOrderNode(node, callback) {
if (node !== null) {
this.inOrderNode(node.left, callback);
callback(node.key);
this.inOrderNode(node.right, callback);
}
}
// 后序遍历
postOrder(callback) {
this.postOrderNode(this.root, callback);
}
postOrderNode(node, callback) {
if (node !== null) {
this.postOrderNode(node.left, callback);
this.postOrderNode(node.right, callback);
callback(node.key);
}
}
}
let tree = new BinarySearchTree();
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);
console.log(tree.remove(14))
console.log(tree.remove(13))
// console.log(tree.remove(11))
console.log(tree)
tree.inOrder((key) => console.log(key));
// 查找
console.log(tree.search(15))
PS:笔记内容来自 极客时间 数据结构与算法之美