一、什么是(AVL)平衡二叉搜索树?
平衡二叉搜索树在二叉树的概念基础上增加了平衡的概念,所谓平衡就是左右子树的高度差不能大于 1(小于或者等于1都可称作平衡的)。所以为了让二叉树达到平衡,又可以引入四种调节平衡的操作:左旋转、右旋转、左平衡、右平衡。
二、四种旋转的具体过程及如何实现?
1、左旋转:
当右孩子的右子树太高的时候,用左旋转进行调整,如图:
代码实现参考:
/**
* 以参数node为根节点进行左旋操作,把旋转后的树的根节点返回
*/
private AVLNode<T> leftRotate(AVLNode<T> node){
AVLNode<T> child = node.getRight();
node.setRight(child.getLeft());
child.setLeft(node);
node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1);
child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1);
return child;
}
2、右旋转:
当左孩子的左子树太高的时候,用右旋转进行调整,过程如图:
代码实现参考:
/**
* 以参数node为根节点进行右旋操作,把旋转后的树的根节点返回
*/
private AVLNode<T> rightRotate(AVLNode<T> node){
AVLNode<T> child = node.getLeft();
node.setLeft(child.getRight());
child.setRight(node);
node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1);
child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1);
return child;
}
3、左平衡:
当左孩子的右子树太高的时候,用左平衡调整,即先左旋再右旋:
代码实现参考:
/**
* 以参数node为根节点进行左平衡操作,把旋转后的树的根节点返回
*/
private AVLNode<T> leftBalance(AVLNode<T> node){
node.setLeft(leftRotate(node.getLeft()));
return rightRotate(node);
}
4、右平衡:
当右孩子的左子树太高的时候,用右平衡操作进行调整,即先右旋再左旋:
代码实现参考:
/**
* 以参数node为根节点进行右平衡操作,把旋转后的树的根节点返回
*/
private AVLNode<T> rightBalance(AVLNode<T> node){
node.setRight(rightRotate(node.getRight()));
return leftRotate(node);
}
三、用法:
在进行二叉搜索树的插入或者删除操作的时候,可能因为插入值或者删除某个节点元素二导致二叉搜索树失衡,这样就到了用上述四种调整的时候啦!
1、在插入元素的时候:
-
从根节点root开始与待插入元素data比较,data>root向右子树寻找,data<root向左子树寻找,直到找到合适data值插入的位置;
-
插入元素后比较左子树高度-右子树高度 是否大于1,大于1,左子树高进入下列情况1,右子树高进入下列情况2:
-
情况1:
若左孩子的左子树高于右子树==》用右旋转;
若左孩子的右子树高于左子树==》用左平衡; -
情况2:
若右孩子的右子树高于左子树==》用左旋转;
若右孩子的左子树高于右子树==》用右平衡;
综合应用于插入删除操作,参考代码如下:
/**
* 递归实现AVL树的插入操作
*/
public void insert(T data){
this.root = insert(this.root, data);
}
/**
* 以参数root为起始节点,搜索一个合适的位置添加data,然后把子树的根节点返回
*/
private AVLNode<T> insert(AVLNode<T> root, T data) {
if(root == null){
return new AVLNode<>(data, null, null, 1);
}
if(root.getData().compareTo(data) > 0){
root.setLeft(insert(root.getLeft(), data));
// 判断root节点是否失衡 #1
if(height(root.getLeft()) - height(root.getRight()) > 1){
if(height(root.getLeft().getLeft())
>= height(root.getLeft().getRight())){
// 左孩子的左子树太高
root = rightRotate(root);
} else {
// 左孩子的右子树太高
root = leftBalance(root);
}
}
} else if(root.getData().compareTo(data) < 0){
root.setRight(insert(root.getRight(), data));
// 判断root节点是否失衡 #2
if(height(root.getRight()) - height(root.getLeft()) > 1){
if(height(root.getRight().getRight())
>= height(root.getRight().getLeft())){
// 右孩子的右子树太高
root = leftRotate(root);
} else {
// 右孩子的左子树太高
root = rightBalance(root);
}
}
}
// 递归回溯过程中,更新节点的高度值 #3
root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
return root;
}
/**
* 实现AVL树的递归删除
*/
public void remove(T data){
this.root = remove(this.root, data);
}
private AVLNode<T> remove(AVLNode<T> root, T data) {
if(root == null){
return null;
}
if(root.getData().compareTo(data) > 0){
root.setLeft(remove(root.getLeft(), data));
// #1
if(height(root.getRight()) - height(root.getLeft()) > 1){
if(height(root.getRight().getRight())
>= height(root.getRight().getLeft())){
root = leftRotate(root);
} else {
root= rightBalance(root);
}
}
} else if(root.getData().compareTo(data) < 0){
// #2
root.setRight(remove(root.getRight(), data));
if(height(root.getLeft()) - height(root.getRight()) > 1){
if(height(root.getLeft().getLeft())
>= height(root.getLeft().getRight())){
root = rightRotate(root);
} else {
root= leftBalance(root);
}
}
} else {
if(root.getLeft() != null && root.getRight() != null){
// #3 左右子树哪个高,删除哪个,为了防止删除带来的旋转操作,提高效率
if(height(root.getLeft()) >= height(root.getRight())){
// 用前驱替换
AVLNode<T> pre = root.getLeft();
while(pre.getRight() != null){
pre = pre.getRight();
}
root.setData(pre.getData());
root.setLeft(remove(root.getLeft(), pre.getData())); //删除前驱
} else {
// 用后继替换
AVLNode<T> post = root.getRight();
while(post.getLeft() != null){
post = post.getLeft();
}
root.setData(post.getData());
root.setRight(remove(root.getRight(), post.getData())); // 删除后继
}
} else {
if(root.getLeft() != null){
return root.getLeft();
} else if(root.getRight() != null){
return root.getRight();
} else {
return null;
}
}
}
// 递归回溯过程中,更新节点的高度值 #4
root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
return root;
}
四、时间复杂度及优缺点?
AVL树的插入操作需要O(log n)的遍历时间和至多两次旋转;
AVL树的删除操作需要O(log n)的遍历时间的至多O(log 2 n)的旋转;
AVL的插入删除都是O(log 2 n)的时间复杂度。
缺点:
AVL树为了保持树的平衡,可能会进行很多次旋转操作,所以在数据量超大的时候,可能时间花费也会很大。但是注意,它不一定比后面所学的红黑树时间复杂度就大,不一定效率就不高,而是取决于具体数据具体情况。