数据结构07-AVL树
一、AVL树的基本概念
1.AVL树
AVL树是一种每一个节点的左子树与右子树的高度差最多等于1的自平衡二叉查找树。
AVL树查找效率比一般的二叉查找树,但是插入删除效率低。
2.平衡因子
平衡因子(BF:BalanceFactor)是二叉树上节点的左子树深度减去右子树深度的值。
3.最小不平衡子树
最小不平衡子树是距离插入节点最近,且平衡因子的绝对值大于1的节点为根的子树。
新增一个节点A后,如果树的深度+1,那么A的所有父节点的平衡因子的绝对值都会+1,距离A最近,且平衡因子的绝对值大于1的节点B就是导致整棵树不平衡的因素,因为B的所有父节点的平衡因子,在插入B之前肯定为0。
二、AVL树的操作
AVL树的插入经常需要进行旋转:左旋和右旋。
1.左旋
左旋就是将节点A变成A的右子节点B的左子节点,并将B原来的左子节点变出A的右子节点,即把A向左下移一位。
public void leftRoate(Node<E> node) {
if (node == null) {
return;
}
Node<E> right = node.right;
// 1.把node的右子节点的左节点变成node的右子节点
node.right = right.left;
if (right.left != null) {
right.left.parent = node;
}
// 2.把node的右子节点变出node的父节点的子节点
right.parent = node.parent;
if (node.parent == null) {
root = right;
} else if (node.parent.right == node) {
node.parent.right = right;
} else if (node.parent.left == node) {
node.parent.left = right;
}
// 3.把node变成node的右子节点的左子节点
right.left = node;
node.parent = right;
}
2.右旋
右旋就是将节点A变成A的左子节点B的右子节点,并将B原来的右子节点变出A的左子节点,即把A向右下移一位。
public void rightRoate(Node<E> node) {
if (node == null) {
return;
}
Node<E> left = node.left;
// 1.把node的左子节点的右节点变成node的左子节点
node.left = left.right;
if (left.right != null) {
left.right.parent = node;
}
// 2.把node的左子节点变出node的父节点的子节点
left.parent = node.parent;
if (node.parent == null) {
root = left;
} else if (node.parent.left == node) {
node.parent.left = left;
} else if (node.parent.right == node) {
node.parent.right = left;
}
// 3.把node变成node的左子节点的右子节点
left.right = node;
node.parent = left;
}
三、AVL树的插入
AVL树的插入很复杂,可以分为三步:添加、检查平衡、修正平衡。
这里把新增节点记作A,把A的父节点记作B,把找到的最小不平衡子树记作C。
1.添加
第一步的添加跟二叉搜索树的插入一样。
2.检查平衡
(1)如果A是左子节点,则B的平衡因子+1;如果A是右子节点,则B的平衡因子-1。
(2)判断B的平衡因子:
- 如果=0,说明整颗树的深度没变,则B的所有父节点的平衡因子不受影响,所以整棵树还是平衡的,不需要修正;
- 如果=1或=-1,说明整颗树的深度变了,则遍历其父节点,并重复(1)、(2)的操作,直到找到平衡因子的绝对值=2的父节点,即为最小不平衡子树C。
3.修正平衡
修正上一步找到的最小不平衡子树C,可以分为4种情况:
- 单向右旋RR:由于在C的左子节点的左子树上插入节点,C的平衡因子由1增至2,致使以C为根的子树失去平衡,则需进行一次C的右旋转操作;
- 单向左旋LL:由于在C的右子节点的右子树上插入节点,C的平衡因子由-1变为-2,致使以C为根的子树失去平衡,则需进行一次C的左旋转操作;
- 先左后右LR:由于在C的左子节点的右子树上插入节点,C的平衡因子由1增至2,致使以C为根的子树失去平衡,则需进行两次旋转,先对C的左子节点左旋,再对C右旋。
- 先右后左RL:由于在C的右子节点的左子树上插入节点,C的平衡因子由-1变为-2,致使以C为根的子树失去平衡,则需进行两次旋转,先对C的右子节点右旋,再对C左旋。
4.代码实现
添加元素:
public void put(E data) {
Node<E> now = new Node<E>(data);
if (root == null) {
root = now;
return;
}
Node<E> parent = root;
// 添加:像二叉查找树一样添加
while (parent != null) {
if (data.compareTo(parent.data) < 0) {
if (parent.left == null) {
parent.left = now;
now.parent = parent;
break;
} else {
parent = parent.left;
}
} else if (data.compareTo(parent.data) > 0) {
if (parent.right == null) {
parent.right = now;
now.parent = parent;
break;
} else {
parent = parent.right;
}
} else {
return;
}
}
// 检查平衡,修正位置
while (parent != null) {
// 如果新增节点是左子节点,则其父节点的平衡因子+1
if (data.compareTo(parent.data) < 0) {
parent.bran++;
// 如果新增节点是右子节点,则其父节点的平衡因子-1
} else {
parent.bran--;
}
if (parent.bran == 0) {
break;
} else if (Math.abs(parent.bran) == 2) {
// 出现平衡问题
fixAfterInsertion(parent);
break;
} else {
parent = parent.parent;
}
}
}
修正位置:
private void fixAfterInsertion(Node<E> parent) {
if (parent.bran == 2) {
leftBranch(parent);
}
if (parent.bran == -2) {
rightBranch(parent);
}
}
对左子树操作:
public void leftBranch(Node<E> node) {
if (node == null) {
return;
}
Node<E> left = node.left;
//单向右旋
if (left.bran == LH) {
rightRoate(node);
//平衡因子都变成0
node.bran = EH;
left.bran = EH;
//先左旋,再右旋。
} else if (left.bran == RH) {
Node<E> lr = left.right;
leftRoate(left);
rightRoate(node);
//2次旋转后,left变出lr的左子节点,node变出lr的右子节点
if (lr.bran == LH) {
node.bran = RH;
left.bran = EH;
lr.bran = EH;
} else if (lr.bran == RH) {
node.bran = EH;
left.bran = LH;
lr.bran = EH;
// lr为叶子节点时
} else if (lr.bran == EH) {
node.bran = EH;
left.bran = EH;
lr.bran = EH;
}
}
}
对右子树操作:
public void rightBranch(Node<E> node) {
if (node == null) {
return;
}
Node<E> right = node.right;
//单向左旋
if (right.bran == RH) {
leftRoate(node);
//平衡因子都变成0
node.bran = EH;
right.bran = EH;
//先右旋,再左旋。
} else if (right.bran == LH) {
Node<E> rl = right.left;
rightRoate(right);
leftRoate(node);
//2次旋转后,right变出rl的右子节点,node变出rl的左子节点
if (rl.bran == LH) {
node.bran = EH;
right.bran = RH;
rl.bran = EH;
} else if (rl.bran == RH) {
node.bran = LH;
right.bran = EH;
rl.bran = EH;
// rl为叶子节点时
} else if (rl.bran == EH) {
node.bran = EH;
right.bran = EH;
rl.bran = EH;
}
}
}
四、AVL树的删除
从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。
代码暂无…
五、AVL树的查找
AVL树的查找与二叉搜索树的查找一样。
最后
数据结构与算法专题:https://www.jianshu.com/nb/25128590
喜欢请点赞,谢谢!