概述
-
平衡
- 任意节点的左右子树高度差小于等于1
-
为什么需要avl?
- avl是一种二叉搜索树,它可以让二叉搜索树的搜索速度保持在logn而不是退化成链表
-
如何在添加或删除时能保证树还是avl呢
- 首先判断是否平衡
- 不平衡的话是哪种不平衡
- 针对这种不平衡的情况该如何处理(旋转)
- 怎么代码实现旋转呢
和二叉树在代码上的区别
1.每进行添加或删除操作,都要检查是否平衡
,如果不平衡需要旋转
(递归)
2.为了检查平衡是否平衡,维护每个节点的高度
,当然也可以现求
3.平衡因子=左子树高度-右子树高度
,通俗来说就是用于比较两棵子树的高度
准备
求高度
int getHeight(TreeNode node) {
if (node == null)
return 0;
return node.height;
}
求平衡因子
int getBalanceFactor(TreeNode node) {
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
旋转
旋转用于处理不平衡的情况,一共有两种旋转(左旋右旋)
下面是右旋
(左旋让左边变低,右旋让右边变低,而这个过程很像围绕着蓝色节点旋转)
TreeNode R(TreeNode oldRoot) {
// 保留两个节点引用
TreeNode newRoot = oldRoot.left;
TreeNode leftRight = oldRoot.left.right;
// ===只需要两步修改===
newRoot.right = oldRoot;
oldRoot.left = leftRight;
return newRoot;
}
完整的左右旋和高度维护
TreeNode L(TreeNode oldRoot) {
// 保留两个节点引用
TreeNode newRoot = oldRoot.right;
TreeNode rightLeft = oldRoot.right.left;
// 改变node的右子树和right的左子树
newRoot.left = oldRoot;
oldRoot.right = rightLeft;
// 自下向上修改高度
oldRoot.height = Math.max(getHeight(oldRoot.left), getHeight(oldRoot.right)) + 1;
newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
return newRoot;
}
TreeNode R(TreeNode oldRoot) {
// 保留两个节点引用
TreeNode newRoot = oldRoot.left;
TreeNode leftRight = oldRoot.left.right;
// 改变node的左子树和left的右子树
newRoot.right = oldRoot;
oldRoot.left = leftRight;
// 自下向上修改高度
oldRoot.height = Math.max(getHeight(oldRoot.left), getHeight(oldRoot.right)) + 1;
newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
return newRoot;
}
不平衡的四种情况的处理
RR指右子树的右节点插入导致不平衡
处理
- RR 左旋
- LL 右旋
- RL 右节点右旋—RR—左旋
- LR 左节点左旋—LL—右旋
判断四种情况
1.首先通过这个节点的平衡因子来判断是否平衡,如果不平衡是哪一棵子树树高度过高
例如 getBalanceFactor(node) > 1 代表右子树过高
2.通过右子树的平衡因子来判断是RR还是RL
例如 getBalanceFactor(node.right) > 0 是 RL
- RL处理方式
if (getBalanceFactor(node) < -1 && getBalanceFactor(node.right) >= 0) {
node.right = R(node.right);
return L(node);
}
完整的情况判断+情况处理
TreeNode rotate(TreeNode node) {
if (getBalanceFactor(node) > 1 && getBalanceFactor(node.left) > 0) {
return R(node);
}
if (getBalanceFactor(node) < -1 && getBalanceFactor(node.right) < 0) {
return L(node);
}
if (getBalanceFactor(node) > 1 && getBalanceFactor(node.left) <= 0) {
node.left = L(node.left);
return R(node);
}
if (getBalanceFactor(node) < -1 && getBalanceFactor(node.right) >= 0) {
node.right = R(node.right);
return L(node);
}
return node;
}
添加
原添加代码+rotate()+高度维护
public void add(int n) {
root = add(root, n);
}
TreeNode add(TreeNode node, int n) {
if (node == null)
return new TreeNode(n);
if (n < node.val)
node.left = add(node.left, n);
else if (n > node.val)
node.right = add(node.right, n);
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
return rotate(node);
}
删除
原删除代码+rotate()+维护高度
public void remove(int n) {
remove(root, n);
}
TreeNode remove(TreeNode node, int n) {
if (node == null)
return null;
if (n < node.val) {
node.left = remove(node.left, n);
// return node;
} else if (n > node.val) {
node.right = remove(node.right, n);
// return node;
} else {
// 要删除的节点无子树或者只有一颗子树
if (node.left == null) {
TreeNode right = node.right;
node.right = null;
node = right;
} else if (node.right == null) {
TreeNode left = node.left;
node.left = null;
node = left;
} else {
TreeNode newNode = bigNode(node.left);
remove(newNode.val);
node.val = newNode.val;
}
}
// 旋转处理,注意node可能为null
if (node == null) return null;
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
return rotate(node);
}
问题
- height维护时只使用子树的height就可以吗
高度是从底部开始计算的,旋转并没有改变子树以下的结构 - 旋转对父节点的影响(个人理解)
- 子树通过旋转平衡后高度改变会影响父节点的平衡
- 添加后子树影响平衡,旋转后树的高度不变
- 删除后子树影响高度,旋转后树的高度会发生变化
- 所以理论上添加操作只需要一次旋转
- getBalanceFactor(node.left) <= 0 这种情况需要等号
- 添加时 要么改变高度,要么将某个节点的度变为2,所以理论上不需要等号
- 删除时 会出现这种情况,所以=是必要的