定义
二叉搜索树:一种树型结构,每个节点的值都大于它左子数的任一节点,并且小于右子树的任一节点
那么树中
- 最小值就是 左子树一路向左查找,左子树为空则根节点最小
- 最大值就是 右子树一路向右查找,右子树为空则根节点最大
二叉搜索树插入
插入流程:
- 从根节点开始对比,如果大于则放到右子树,小于放到左子数
- 从1中的新的节点开心进行对比,直到位置 为空可以存储此数据
// 插入 1 // 和15对比小于,则放到左边,和9进行对比,继续...
(1)15 15 15 15
/ \ / \ / \ / \
9 23 ==> (1)9 23 ==> 9 23 ===> 9 23
/ \ / \ / \ / \ / \ / \ / \ / \
3 12 17 28 3 12 17 28 (1)3 12 17 28 3 12 17 28
\ \ \ / \
8 8 8 1 8
代码示例
插入的时间复杂度:O(logn)
const insert = (root, value) => {
const newNode = { val: value, left: null, right: null }
if (!root) {
return newNode
}
let node = root
while(node) {
if (value > node.val) {
if (!node.right) {
node.right = newNode
return
} else {
node = node.right
}
} else {
if (!node.left) {
node.left = newNode
return
} else {
node = node.left
}
}
}
return root
}
二叉搜索树删除
删除节点node 流程:
- 叶子节点,直接删除,把node的parent对于node的指向置空
- 只有一个子节点child,则删除node,然后把子节点child作为新的跟节点,即把node的parent对于node的指向改为child
1 1 1
/ ==> 删除节点2 ==> / ==> 节点3替换 ==> /
2 x 3
/ / / \
3 3 4 5
/ \ / \
4 5 4 5
- 删除的节点有左节点和右节点,则删除节点node,然后node的左子树的最大值,就是node的左子树的最右子节点pNode(一路向右),然后把pNode的值作为node的新值,删除的转移成删除pNode
15 15
/ \ / \
9 23 9 23
/ \ ===> 删除节点9 ===> 找到左子树最大值4 ==> / \ ===>
3 12 17 3 12 17
/ \ / \
1 4 1 (4)
==> 替换9变成4,然后删除4
15
/ \
4 23
/ \
3 12 17
/ \
1
代码示例
删除的时间复杂度:O(logn)
const del = (root, value) => {
let parent = null
let node = root
while(node && node.val !== value) {
parent = node
if (value > node.val) {
node = node.right
} else {
node = node.left
}
}
// 未找到节点
if (!node) {
return root
}
// 左右子树都存在的节点,需要找到左子树中最右的节点
// 将最右节点的值替换到node上,然后删除的节点则转移成了,最右节点
if (node.left && node.right) {
// 缓存节点,由于值处理
const temp = node
// 变量修改
parent = node
// 从node的左子树开始找
node = node.left
while(node.right) {
parent = node
node = node.right
}
// 找到了,值进行替换
temp.val = node.val
}
// 因为上面找到的最右节点一定是一个右节点为空的节点,因此可以进行下面的判断
// 对于只有一个子节点的情况,删除的时候
// 只需要把存在的子节点替换父节点即可,此处是直接替换,不是值替换
// 对于叶子节点的删除,只需要父节点删除对于的子节点即可,此时child = null
const child = node.left || node.right
// 如果是根节点,并且没有左子树或者右子树,那直接左子树/右子树
if (!parent) {
return child
}
if (parent.left === node) {
parent.node = child
} else {
parent.right = child
}
}
二叉搜索树的查找
查找流程:
- 从根节点开始比较,大于则往右子树查找,小于往左子树查找
代码示例
查找的平均时间复杂度是树高 O(logn)
const find = (root, target) => {
let node = root
while(node && node.val !== target) {
if (target > node.val) {
node = node.right
} else {
node = node.left
}
}
return !!(node && node.val === target)
}
最佳二叉搜索树
二叉搜索树查找的时间复杂度是树高,平均复杂度是O(logn),但是当最大值或者最小值作为树根的时候,树的高度会变成n,远大于平均复杂度,因此尽可能的平均左右子树的节点树,可以有效的降低树高。
让中位数做树的根节点
代码实现从1~n的节点构建一个最佳二叉树
const createBestBinarySearchTree = (start, end) => {
if (start > end) {
return null
}
const mid = Math.floor((start + end) / 2)
const root = { val: mid, left: null, right: null }
root.left = createBestBinarySearchTree(start, mid - 1)
root.right = createBestBinarySearchTree(mid + 1, end)
return root
}
平衡二叉搜索树
对于最佳二叉搜索树进行多次的插入和删除后,会变成非最佳二叉排序树,因为随着插入和删除,树根可能不是中位数了,相最大/最小偏移,影响查找速度,因此要动态的保持查找效率,需要利用平衡二叉树,也就是平衡二叉搜索树