在计算机科学中,AVL树是最先发明的自平衡二叉搜索树,在AVL树中任何结点的两个子树的高度差不大于1,所以它也会被称为高度平衡树。增加和删除可能需要通过一次或多次树的旋转来重新平衡这个树。
也就是说,AVL树,本质上是带了平衡功能的二叉搜索树(AVL = BST + 平衡);
这里要注意的是,左旋操作之后,node.child这个结点会发生改变,代码中相应的要对node进行setLeft()操作来改变node的左孩子;
同样的,这里也应该对node进行setRight()操作来改变node的右孩子;
AVL树的数据结构:
class AVL<T extends Comparable<T>> {
private AVLNode<T> root;
public AVL() {
this.root = null;
}
// 获取以node为根节点的树的高度
private int height(AVLNode<T> node) {
return node == null ? 0 : node.getHeight();
}
// 获取以node1h和node2为根节点的子树的最大高度值
private int maxHeight(AVLNode<T> node1, AVLNode<T> node2) {
return height(node1) > height(node2) ? height(node1) : height(node2);
}
}
/**
* @Author Daria
* @Description AVL树的节点类型:在BST树的基础上新增了每个节点的高度属性
* @Date 2019/7/11 -- 23:33
*/
class AVLNode<T extends Comparable<T>> {
private T data;
private AVLNode<T> left;
private AVLNode<T> right;
private int height; //结点的高度
public AVLNode(T data, AVLNode<T> left, AVLNode<T> right, int height) {
this.data = data;
this.left = left;
this.right = right;
this.height = height;
}
public AVLNode(T data, int height) {
this.data = data;
this.left = null;
this.right = null;
this.height = height;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public AVLNode<T> getLeft() {
return left;
}
public void setLeft(AVLNode<T> left) {
this.left = left;
}
public AVLNode<T> getRight() {
return right;
}
public void setRight(AVLNode<T> right) {
this.right = right;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
需要AVL树进行旋转操作的情况,其简单图示和伪代码在上面有说到;
// 左旋 --> 右孩子的右子树太高
public AVLNode<T> leftRotate(AVLNode<T> node) {
AVLNode<T> child = node.getRight(); //拿到node节点的右孩子,为child
node.setRight(child.getLeft()); //将child的左孩子变成node的右孩子
child.setLeft(node); //再将child的左孩子设为node
node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1); //更新node的高度
child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1); //更新child的高度
return child;
}
// 右旋 --> 左孩子的左子树太高
public AVLNode<T> rightRotate(AVLNode<T> node) {
AVLNode<T> child = node.getLeft(); //拿到node节点的左孩子,记为child
node.setLeft(child.getRight()); //将child的右孩子变成node的左孩子
child.setRight(node); //将child的右孩子设为node
node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1); //更新node的高度
child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1); //更新child的高度
return child;
}
// 左平衡 --> 左孩子的右子树太高
public AVLNode<T> leftBalance(AVLNode<T> node) {
// 以node.left为根节点左旋
node.setLeft(leftRotate(node.getLeft()));
// 以node为根节点右旋
return rightRotate(node);
}
// 右平衡 --> 右孩子的左子树太高
public AVLNode<T> rightBalance(AVLNode<T> node) {
//以node.right为根节点右旋
node.setRight(rightRotate(node.getRight()));
//以node为根节点左旋
return leftRotate(node);
}
//递归实现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, 1);
}
if (root.getData().compareTo(data) > 0) {
root.setLeft(insert(root.getLeft(), data));
//判断root结点是否失衡
if (height(root.getLeft()) - height(root.getRight()) > 1) { //若在此处插入成功则root的左子树高度会发生改变,从而导致root节点失衡
// 判断data是root左子树的左孩子还是右孩子
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结点是否失衡
if (height(root.getRight())- height(root.getLeft()) > 1) { //若在此处插入成功则root的右子树高度会发生改变,从而导致root节点失衡
if (height(root.getRight().getRight()) >= height(root.getRight().getLeft())) { //右孩子的右子树太高
root = leftRotate(root);//左旋
} else { //右孩子的左子树太高,右平衡
root = rightBalance(root);
}
}
}
//回溯过程中更新结点的高度值
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));
//若在此处删除成功,root的右子树与左子树的高度差可能会发生改变,导致root失衡
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){
root.setRight(remove(root.getRight(), data));
//判断root结点是否失衡
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 { //root.getData().compareTo(data) == 0,root就是要删除的节点,在这里进行具体的删除操作
if (root.getLeft() != null && root.getRight() != null) { //root的左右孩子都不为null
//先判断root左子树和右子树的高度,若左子树高就用root前驱的值覆盖root,右子树高就用root后继的值覆盖root
//这样可以防止删除带来的旋转操作,提高效率
if (height(root.getLeft()) >= height(root.getRight())) { //左子树高,删除前驱
AVLNode<T> pre = root.getLeft();
while (pre.getRight() != null) {
pre = pre.getRight();
}
root.setData(pre.getData()); //用pre的值覆盖root的值
root.setLeft(remove(root.getLeft(), pre.getData())); //删除前驱pre
} else { //右子树高就删除后继
AVLNode<T> post = root.getRight();
while (post.getLeft() != null) {
post = post.getLeft();
}
root.setData(post.getData()); //用post的值覆盖root的值
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; //root没有孩子节点
}
}
}
//回溯过程中更新结点的高度值
root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
return root;
}