一、AVL树的基础知识
1. AVL树是BST树的一种,BST的性质均在AVL树中同样适用,但在查找等方面效率不同
2.AVL树对BST树的优化: 当插入的数据是按顺序排好时,那么在BST树中形成的是一个单链表,查询等效率便会降低,而AVL树很好的解决了这一问题。
3. 所谓的平衡即是指:每个结点的两个子树的高度差值为0或者1.
4. 平衡因子:一个结点的平衡因子是指它的左子树的高度减去右子树的高度(也有说是右子树的高度减去左子树的高度,这个影响不大)。则AVL树中结点的平衡因子只能为1、0或者-1.
二、 AVL树中的操作与BST树中操作的不同点
1. AVL树可以说是BST树的一种优化类型,它的大部分实现思路源自于BST树,但是在插入和删除结点之后,树的平衡性可能发生改变,因此需要重新判断树是否满足平衡条件,若不平衡,还要采取措施让树平衡。
2. 对树进行平衡与否检测的时机:当有新的结点插入或者删除时。
3. 应该对那些结点进行平衡检测:
3.1 插入操作:从新插入结点的父结点开始,沿着到这个结点的反向路径逐个检测。(由于新插入的结点一定是叶子结点,因此它一定是平衡的,不需要进行检测)。
3.2 删除操作:从删除结点的那个子树形成的根结点开始,沿着到这个结点的方向路径进行检测。
三、AVL类设计
与BST类的内部结点类相比,AVL类内部结点类需要新增一个记录结点高度的属性
public class AVLTree<E extends Comparable<E>> {
//内部结点类
private class Node{
public E value; //存储结点值
public Node left; //指向左孩子
public Node right; //指向右孩子
int height; //增加项:记录当前结点所处的高度(规定叶子结点高度为1)
public Node(E value) {
this.value = value;
this.left = null;
this.right = null;
this.height = 1; //所形成的结点一定会插入到AVLTree的叶子结点处,即高度为1
}
}
private Node root; //根结点
private int size; //树中结点个数
public AVLTree() {
this.root = null; //初始时为一棵空树
this.size = 0;
}
}
四、辅助方法
1. 获取结点高度:
由于结点中记录有高度值,故直接返回即可。
//获取结点的高度
public int getHeight(Node node) {
if(node == null)
return 0;
return node.height;
}
2. 获取平衡因子
平衡因子 = 左子树高度 - 右子树高度;
//获取平衡因子
public int getBalanceFactor(Node node) {
if(node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
3. 为了判断平衡后的树是否满足BST的条件以及AVL的条件,这里提供两个方法进行判断:
3.1 判断是否还是BST树
实现思路:使用中序遍历得到的将是一列排好序的数据,通过判断中序遍历得到的数据是否是排好序的即可判断树是否还为BST树。
//判断是否为BST树
public boolean isBST(Node node){
ArrayList<E> list = new ArrayList<>();
inOrder(node,list);
for(int i = 1; i < list.size(); i++){
if(list.get(i - 1).compareTo(list.get(i)) > 0)
return false;
}
return true;
}
3.2 判断是否还是AVL树
实现思路:通过判断每个结点的平衡因子是否小于等于1进行检测。
//判断树是否为平衡的
public boolean isBalance() {
return isBalance(root);
}
private boolean isBalance(Node node) {
if(node == null)
return true;
int balanceFactor = getBalanceFactor(node);
if(Math.abs(balanceFactor) > 1)
return false;
return isBalance(node.left) && isBalance(node.right);
}
五、 四种平衡机制
1. LL
如图所示:结点A的平衡因子为2,它的左孩子结点的平衡因子为1或者0,这种类型可通过将结点A向右旋转进行平衡修复。对于插入操作相当于是插入的结点在当前结点的左孩子的左子树中。
代码实现:
//LL 进行右旋转
private Node rightRotate(Node y) {
Node x = y.left;
y.left = x.right;
x.right = y;
//更新height 此时y为x的孩子结点,因此需要先更新y的高度,再更新x的高度(x的高度与y的高度有关)
y.height = Math.max(getHeight(y.left), getHeight(y.right));
x.height = Math.max(getHeight(x.left), getHeight(x.right));
return x;
}
2. RR
RR与LL的思路相同,它们两个是对称的
如图所示:结点A的平衡因子为-2,它的右孩子结点的平衡因子为-1或者0,这种类型可通过将结点A向左旋转进行平衡修复。对于插入操作相当于是插入的结点在当前结点的右孩子的右子树中。
代码实现:
//RR 左旋转
private Node leftRotate(Node y) {
Node x = y.right;
y.right = x.left;
x.left = y;
//更新height
y.height = Math.max(getHeight(y.left),getHeight(y.right));
x.height = Math.max(getHeight(x.left),getHeight(x.right));
return x;
}
3. LR
如图所示:结点A的平衡因子为2,A的左孩子结点B的平衡因子为-1,这种类型需要通过两次旋转来实现树的平衡,先将结点B向左旋转一次,然后再将结点A向右旋转一次。对于插入操作相当于是插入的结点在当前结点的左孩子的右子树中。
实现代码复用leftRotate()和rightRotate();
4. RL
如图所示:结点A的平衡因子为-2,A的右孩子结点B的平衡因子为1,这种类型需要通过两次旋转来实现树的平衡,先将结点B向右旋转一次,然后再将结点A向左旋转一次。对于插入操作相当于是插入的结点在当前结点的右孩子的左子树中。
实现代码复用leftRotate()和rightRotate();
六、AVL树的插入操作
实现思路:大部分实现代码借用BST树的插入操作的代码,之后在插入之后需要重新计算结点的高度以及对树进行重新平衡(如何平衡在第二点中已经说明)。
代码实现:
//向树中添加结点
public void add(E value) {
root = add(root,value);
}
//添加结点时要满足BST的规则,增加结点会导致相关结点所处的高度的改变,
//同时可能会破坏树的平衡性,因此需要重新更新结点高度以及平衡树。
private Node add(Node node, E value) {
if(node == null) {
size++;
return new Node(value);
}
if(value.compareTo(node.value) < 0) {
node.left = add(node.left,value);
}
else if(value.compareTo(node.value) > 0){
node.right = add(node.right,value);
}
else {
throw new IllegalArgumentException("Arguments is illegal!");
}
//更新结点的高度 = 左右子树中高度的最大值 + 1
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
//平衡因子计算
int balanceFactor = getBalanceFactor(node);
//对不平衡的结点重新进行平衡
//1. 右旋转
if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
//2. 左旋转
if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
//3. LR
if(balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
//4. RL
if(balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
七、AVL树的删除操作
//获取最小值所在的结点
private Node minimum(Node node) {
if(node.left == null) //node.left == null ,那么结点node即为最小结点
return node;
return minimum(node.left); //递归遍历
}
//删除操作
public void remove(E value) {
if(value == null)
throw new IllegalArgumentException("Argument Illegal!");
root = remove(root,value);
}
private Node remove(Node node, E value) {
if(node == null)
return null;
int result = value.compareTo(node.value); //比较当前结点与要删除的结点值的大小
Node currentNode;
//要删除的结点在当前结点的左子树中
if(result <0) {
node.left = remove(node.left,value);
currentNode = node;
}
//要删除的结点在当前结点的右子树中
else if(result > 0) {
node.right = remove(node.right,value);
currentNode = node;
}
//当前结点即为要进行删除的结点
else {
//左子树为空
if(node.left == null) {
Node nodeRight = node.right;
node.right = null;
size--;
currentNode = nodeRight;
}
//右子树为空
else if(node.right == null) {
Node nodeLeft = node.left;
node.left = null;
size--;
currentNode = nodeLeft;
}
//要删除结点的左右子树均不为空
else {
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node minNode = minimum(node.right);
minNode.right = remove(node.right, minNode.value);
minNode.left = node.left;
node.left = node.right = null;
currentNode = minNode;
}
}
if(currentNode == null) {
return null;
}
//更新结点高度
currentNode.height = 1 + Math.max(getHeight(currentNode.left),
getHeight(currentNode.right));
//计算平衡因子
int balanceFactor = getBalanceFactor(currentNode);
// 平衡操作
// 1. 右旋转
if (balanceFactor > 1 && getBalanceFactor(currentNode.left) >= 0)
return rightRotate(currentNode);
// 2. 左旋转
if (balanceFactor < -1 && getBalanceFactor(currentNode.right) <= 0)
return leftRotate(currentNode);
// 3. LR
if (balanceFactor > 1 && getBalanceFactor(currentNode.left) < 0) {
currentNode.left = leftRotate(currentNode.left);
return rightRotate(currentNode);
}
// 4. RL
if (balanceFactor < -1 && getBalanceFactor(currentNode.right) > 0) {
currentNode.right = rightRotate(currentNode.right);
return leftRotate(currentNode);
}
return currentNode;
}