二叉平衡树(AVL 树)
二叉平衡树(AVL 树)是一种自平衡的二叉搜索树,它通过保持树的平衡性来提供快速的搜索、插入和删除操作。它在设计上确保了左子树和右子树的高度差不超过 1,从而保持了树的平衡。
特点
- 每个节点都有一个平衡因子(Balance Factor),它表示左子树高度和右子树高度之差。平衡因子可以是 -1、0 或 1。
- 每个节点的左子树和右子树也是 AVL 树。
- AVL 树中的每个节点都存储一个关键字,关键字按照一定的顺序排列。
平衡操作
当插入或删除节点后,可能会破坏 AVL 树的平衡性。为了恢复平衡,AVL 树使用四种基本的平衡操作:
- 左旋转(Left Rotation):在一个节点的右子树上进行旋转操作,使得右子树的根节点成为新的根节点,原来的根节点成为新根节点的左子树。
- 右旋转(Right Rotation):在一个节点的左子树上进行旋转操作,使得左子树的根节点成为新的根节点,原来的根节点成为新根节点的右子树。
- 左-右双旋转(Left-Right Double Rotation):先对当前节点的左子树进行右旋转,然后再对当前节点进行左旋转。
- 右-左双旋转(Right-Left Double Rotation):先对当前节点的右子树进行左旋转,然后再对当前节点进行右旋转。
这些平衡操作通过调整树的结构来保持平衡,并且保持所有节点的平衡因子符合 AVL 树的定义。
时间复杂度
- 搜索操作的平均时间复杂度为 O(log n),其中 n 是树中节点的数量。
- 插入和删除操作的平均时间复杂度也为 O(log n),因为在插入或删除后需要执行平衡操作来恢复树的平衡。
举个🌰
将个长度为1w的数组转换为二叉平衡树的前提是必须先将数组排序
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function buildBalancedBST(nums) {
if (nums.length === 0) {
return null;
}
const mid = Math.floor(nums.length / 2); // 找到中间位置
const root = new TreeNode(nums[mid]); // 中间元素作为根节点
// 递归构建左子树和右子树
root.left = buildBalancedBST(nums.slice(0, mid));
root.right = buildBalancedBST(nums.slice(mid + 1));
return root;
}
// 示例用法
const nums = Array.from({ length: 10000 }, (_, index) => Math.Random() * 10000); // 构建 1
到 10000 的数组
nums.sort((a, b) => a -b);
const balancedBST = buildBalancedBST(nums);
console.log(balancedBST);
优点和应用
- AVL 树提供了快速的搜索、插入和删除操作,特别适用于需要频繁执行这些操作的场景。
- AVL 树的平衡性保证了搜索和插入操作的时间复杂度较低,使得它在大量动态数据的情况下仍然能够保持高效。
- AVL 树广泛应用于数据库
获取树的深度
function getDeep(root) {
if (root === null) {
return 0;
}
const left = getDeep(root.left);
const right = getDeep(root.right);
return Math.max(left, right) + 1;
}
判断是否为二叉平衡树
只要每个节点的的左子树和右子树的高度差小于1那么他就是二叉平衡树
function isBalance(root) {
if(root === null) {
return false;
}
const leftDeep = getDeep(root.left);
const rightDeep = getDeep(root.right);
if(Math.abs(leftDeep - rightDeep) > 1) {
return false;
}
return isBalance(root.left) && isBalance(root.right);
}
AVL的右旋和左旋
这个图清晰明了
根节点S的左子树高度大于右子树,所以要向右平衡。
- 将S的左节点E作为新的根节点
- 将E的右节点作为S的左节点
根节点E的右子树高度大于左子树,所以要向左平衡。
- 将E的右节点S作为新的根节点
- 将S的左节点作为E的右节点
二叉树经过左旋后还可以通过右旋将其还原回来。同理右旋也可以通过左旋还原。
代码实现左旋和右旋
function rotateLeft(root) {
const tem = root.right;
tem.left = root;
root.right = tem.left;
return tem;
}
function rotateRight(root) {
const temp = root.left;
temp.right = root;
root.left = temp.right;
return temp;
}
代码实现AVL的平衡
function change(root){
if(isBalance(root)) {
return root;
}
if(root.right !== null) {
root.right = change(root.right)
}
if(root.left !== null) {
root.left = change(root.left)
}
const leftDeep = getDeep(root.left)
const rightDeep = getDeep(root.right)
if(leftDeep > rightDeep) {
// 右旋
return rotateRight(root)
} else {
// 左旋
return rotateLeft(root)
}
}