前言
二叉搜索树可以帮我们按照一定的规则存储数据,方便我们能够快速操作,最差的情况也是O(n)的时间复杂度。
代码实现
节点定义
class Node {
constructor(key) {
this.key = key; //结点值
this.left = null; //左结点引用
this.right = null; //右结点引用
}
}
比较方法定义
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
};
function defaultCompare(a, b) {
if (a === b) {
return 0;
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; // {2}
}
不同于在之前的其他数据结构中将节点本身称作节点或项,我们将会对二叉树中的结点称其为键(key),键是树相关的术语中对节点的称呼。
二叉树相关代码
class BinarySearchTree {
constructor(compareFn = defaultCompare) {
this.compareFn = compareFn; //用来比较节点值的函数
this.root = null; //Node类型的根节点
}
//向二叉搜索树中插入一个键
insert(key) {
if (this.root == null) {
this.root = new Node(key);
} else {
this.insertNode(this.root, key);
}
}
insertNode(node, key) {
//要插入的键比当前遍历到的键小的情况,则开始在左子树中插入
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
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);
}
}
}
//层次遍历
levelOrder(root) { //从上到下bfs
const queue = [];//bfs一般都是队列,因为不需要回溯
const result = [];//保存结果的数组
if (root != null) {
queue.push(root);//把根节点存进去
}
while (queue.length != 0) {
const level = [];//每一层的数据
const len = queue.length;//当前层的元素个数
for (let i = 0; i < len; i++) {
const currentNode = queue.shift();
level.push(currentNode.key);//添加当前层的键
if (currentNode.left !== null) {
queue.push(currentNode.left);//添加当前层的左子树
}
if (currentNode.right !== null) {
queue.push(currentNode.right);//添加当前层的右子树
}
result.push(level);//把当前层的数据存到总数据中
}
return result//最后返回出去就是遍历后的结果
}
}
//中序遍历[最小到最大的顺序访问所有节点]
inOrderTraverse(node) {
if (node != null) {
this.inOrderTraverse(node.left);
console.log(node.key);
this.inOrderTraverse(node.right);
}
}
//先序遍历 先访问自身结点 再去遍历左右
preOrderTraverse(node) {
if (node != null) {
console.log(node.key);
this.preOrderTraverse(node.left);
this.preOrderTraverse(node.right);
}
}
//后序遍历 先访问左子树,然后访问右子树, 再去遍历自身结点
postOrderTraverseNode(node) {
if (node != null) {
this.postOrderTraverseNode(node.left);
this.postOrderTraverseNode(node.right);
console.log(node.key);
}
}
//最小值
min() {
return this.minNode(this.root);
}
minNode(node) { //一直往最左侧寻找
let current = node;
while (current != null && current.left != null) {
current = current.left;
}
return current;
}
max() {
return this.maxNode(this.root);
}
maxNode(node) { //一直往最右侧寻找
let current = node;
while (current != null && current.right != null) {
current = current.right;
}
return current;
}
//总结:寻找最大数就沿着最右边一直寻找 最小就沿着最左侧一直寻找
//搜索特定的值
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node == null) {
return false;
}
//如果要找的比当前遍历到的小,就去左子树找
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
return this.searchNode(node.left, key);
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
//如果要找的比当前遍历到的大,就去右子树找
return this.searchNode(node.right, key);
} else {
return true;
}
}
//remove 使二叉搜索树中最复杂的方法
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
if (ndoe == null) { //如果正在检测的节点为 null ,那么说明键不存在于树中,所以返回 null 。
return null;
}
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
/*如果要找的键比当前节点的值小
,就沿着树的左边找到下一个节点。*/
node.left = this.removeNode(node.left, key);
return node;
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
//如果要找的键比当前节点的值大,
//那么就沿着树的右边找到下一个节点
node.right = this.removeNode(node.right, key);
return node;
} else {
//如果我们找到了要找的键(键和 node.key 相等),就需要处理三种不同的情况
// 第一种情况 移除一个叶子节点
if (node.left == null && node.right == null) {
node = null;
return node;
}
// 第二种情况 移除有一个左侧或右侧子节点的节点
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
// 第三种情况 移除有两个子节点的节点
//当找到了要移除的节点后,需要找到它右边子树中最小的节点(它的后继 (当然也可以找他左子树中最大的结点,叫做他的前驱),这里我们找的是后继
const aux = this.minNode(node.right);
//然后,用它右侧子树中最小节点的键去更新这个节点的值(行 {19} )。通过这一步,我们改变了这个节点的键,也就是说它被移除了。
node.key = aux.key;
// 但是,这样在树中就有两个拥有相同键的节点了,这是不行的。要继续把右侧子树中的最小节点移除,毕竟它已经被移至要移除的节点的位置了
node.right = this.removeNode(node.right, aux.key);
//最后,向它的父节点返回更新后节点的引用
return node;
}
}
}
结语
这样我们就实现了一个二叉搜索树BST