二分搜索树看了我整整三天,又是被人类智慧折服的每一天,冲冲冲!
1. 什么是二分搜索树
二分搜索树(Binary Search Tree, BST),属于树形数据结构的一种,二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树,它的定义如下:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点;
优点
- 相比于其他数据结构的优势在于有着高效的插入、删除、查找操作,平均时间复杂度为O(logn)
- 可以方便地回答数据之间的关系的问题:如min,max,floor,ceil,rank,select
插入 | 删除 | 查找 | |
---|---|---|---|
普通数组 | O(n) | O(n) | O(n) |
顺序数组 | O(n) | O(n) | O(logn) |
二分搜索树 | O(logn) | O(logn) | O(logn) |
普通数组插入O(n):这里实现的是无重复元素的数据结构,所以普通数组插入时需要先查找是否已经存在该元素,如果存在则更新,不存在则插入。
局限性
- 不能随机访问
- 二分搜索树为
不平衡树
,若树分布极不平衡,则会大大影响时间性能 同样的数据,可以对应不同的二分搜索树,如果节点数和树深度相同(类似链表),则所有操作退化为O(n)可改进为平衡二叉搜索树,如红黑树等.本文不做探讨
2. JavaScript实现BST
以下为构建一个无重复元素的二分搜索树(类似字典),可以通过查找 key 得到 value
👇以下为主要结构
// 构造节点
class Node {
constructor(key, value) {
this.key = key
this.value = value
this.left = null
this.right = null
}
}
// 构造二分搜索树
class BST {
constructor(root = null) {
this.root = root
this.count = 0
}
// 主要方法
insert(key){} // 插入
search(key){} // 查找
preOrder(){} // 前序遍历
inOrder(){} // 中序遍历
postOrder(){} // 后序遍历
levelOrder(){} // 广度优先遍历(层序)
searchMin(){} // 查找最小
searchMax(){} // 查找最大
deleteMin(){} // 删除最小
deleteMax(){} // 删除最大
delete(key){} // 删除
}
2.1 插入
// 向以node为根的二叉搜索树中,插入节点(key, value)
// 返回根节点
// 递归实现
insert(key, value) {
return this.root = this._insert(this.root, key, value)
}
_insert(node, key, value) {
if (node === null) {
return node = new Node(key, value)
}
if (key === node.key) {
node.value = value
} else if (key < node.key) {
node.left = this._insert(node.left, key, value)
} else {
node.right = this._insert(node.right, key, value)
}
return node
}
}
2.2 查找
// 向以node为根的二叉搜索树中,搜索是否包含key的节点。
// 包含返回node,不包含则返回null
search(key) {
let node = this.root
while (node !== null) {
if (key === node.key) {
return node
} else if (key > node.key) {
node = node.right
} else {
node = node.left
}
}
return null
}
2.3 遍历
二分搜索树遍历分为两大类,深度优先遍历
和广度优先遍历
。
深度优先遍历
分为三种,先序遍历、中序遍历、后序遍历。(以根的位置划分)
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
// 先序遍历(递归)
preOrder(node = this.root, arr = []) {
if (node !== null) {
arr.push(node.key)
this.preOrder(node.left, arr)
this.preOrder(node.right, arr)
}
return arr
}
// 中序遍历(递归)
inOrder(node = this.root, arr = []) {
if (node !== null) {
this.inOrder(node.left, arr)
arr.push(node.key)
this.inOrder(node.right, arr)
}
return arr
}
// 后序遍历(递归)
postOrder(node = this.root, arr = []) {
if (node !== null) {
this.postOrder(node.left, arr)
this.postOrder(node.right, arr)
arr.push(node.key)
}
return arr
}
// 广度优先遍历(层级)
levelOrder() {
const stack = []
const arr = []
stack.push(this.root)
while (stack.length > 0) {
let node = stack.shift() // 先进先出
arr.push(node.key)
node.left && stack.push(node.left)
node.right && stack.push(node.right)
}
return arr
}
2.4 删除
-
查找要删除的节点 (1)如果该节点不存在,则返回null (2)如果该节点存在,则继续
-
判断
(1)当节点没有子节点,那么只需要将从父节点指向它的链接指向变为null
(2)当节点只有左子树时,父节点指向该节点的子节点
(2)当节点只有右子树时,父节点指向该节点的子节点
(3)【重点】当节点包含左右子树时,该节点替换为左子树中最大的节点或右子树中最小的节点(1962年,Hubbard deletion)
delete(key) {
return this.root = this._deleteNode(this.root, key)
}
_deleteNode(node, key) {
if (node === null) {
return null
}
if (key > node.key) {
node.right = this._deleteNode(node.right, key)
return node
} else if (key < node.key) {
node.left = this._deleteNode(node.left, key)
return node
} else { // key === node.key
if (node.left === null) {
return node.right
} else if (node.right === null) {
return node.left
} else {
// 左右节点均不为空
let rightMinNode = this.searchMin(node.right)
let successor = new Node(rightMinNode.key, rightMinNode.value)
successor.left = node.left
successor.right = this.deleteMin(node.right)
return successor
}
}
}
// delete(key) 需要调用searchMin(),deleteMin()(均为递归)
searchMin(node = this.root) {
if (node.left === null) {
return node
}
node = node.left
return this.searchMin(node)
}
deleteMin(){
// 删除以node为根节点的最小节点
// 返回删除后的根节点
if(this.root === null){
return null
}
return this.root = this._deleteMin(this.root)
}
_deleteMin(node){
if(node.left === null) {
return node.right
}
node.left = this._deleteMin(node.left)
return node
}
更多
除了对数据进行增删改查,二分搜索树还可以回答数据之间的顺序性问题。(具体实现略)
- minimum、maximum
- successor(后继)、predecessor(前驱)
- floor(地板)、ceil(天花板)
- rank(58是排名第几的元素)、select(排名第10的元素是谁)
3. 不同设计的BST
4. 参考
慕课刘宇波【算法与数据结构 】 :大爱波波老师❤️
菜鸟教程-数据结构:具体的实现过程图示,nice!