数据结构之AVL树

原创
2020/03/19 10:42
阅读数 101

平衡二叉查:

        平衡二叉树通常指一棵空树或左右两个子树的高度差的绝对值不超过1,并且任意节点的左右子树都是一棵平衡二叉树,即严格的平衡二叉树。平衡二叉树有多种实现方法:AVL树,红黑树,替罪羊树,伸展树,Treap等。

AVL树:

概念:

AVL树又称为高度平衡的二叉搜索树。它能保持二叉树的高度平衡,尽量降低二叉树的高度,减少树的平均搜索长度。

性质:

  1. 左子树和右子树的高度之差绝对值不超过1。
  2. 树中的每个左子树和右子树都是AVL树。
  3. 每一个节点都有一个平衡因子,任一节点的平衡因子是1、0、-1,平衡因子等于右子树的高度减去左子树的高度。
class AVLNode<T> {
    T element;
    AVLNode<T> left;
    AVLNode<T> right;
    int height;//记录高度

    //构造器
    public AVLNode(T theElement) {
        this(theElement, null, null);
        element = theElement;
    }

    public AVLNode(T theElement, AVLNode<T> left, AVLNode<T> right) {
        this.element = theElement;
        this.left = left;
        this.right = right;
        this.height = 0;
    }
}

操作:

因为AVL树要求左右子树高度差绝对值不超过1,因此每次操作后,必须通过旋转来调节平衡因子,保持平衡。

插入:

AVL树的插入操作首先会按照普通二叉搜索树的插入操作进行。当插入数据后我们会按照插入时的所经过的节点回溯,回溯过程中,会判断每个节点的左子树高度和右子树高度的绝对值之差是否超过1。如果超过1,我们就需要通过旋转进行调整,调整的目的是使该节点满足AVL树的定义。

public void insert(T x) {
        this.root = insert(x, root);
    }
public AVLNode<T> insert(T x, AVLNode<T> t) {
        if (t == null)//传入节点为空,则将该节点作为根节点返回
            return new AVLNode<>(x, null, null);
        int compareResult = x.compareTo(t.element);
        if (compareResult < 0)//传入元素x小于t
            t.left = insert(x, t.left);
        else if (compareResult > 0)//传入元素x大于t
            t.right = insert(x, t.right);
        return balance(t);
    }

删除:

AVL树的删除操作逻辑与插入操作逻辑相同,首先按照普通平衡二叉树进行删除操作,删除后安照删除前查询时所经过的节点进行回溯,回溯后的调整逻辑与插入的调整逻辑相同。

public void remove(T x) {
        this.root = remove(x, root);
    }
public AVLNode<T> remove(T x, AVLNode<T> t) {
        if (t == null)//传入节点为空
            return t;
        int compareResult = x.compareTo(t.element);
        if (compareResult < 0)
            t.left = remove(x, t.left);
        else if (compareResult > 0)
            t.right = remove(x, t.right);
        else if (t.left != null && t.right != null) { //有两个儿子
            t.element = findMax(t.left).element;
            remove(t.element, t.left);
        } else //单儿子情形
            t = t.left == null ? t.right : t.left;
        if (t != null)
            t.height = height(t.right) > height(t.left) ? height(t.right) + 1 : height(t.left) + 1;
        return balance(t);
    }

查询:

AVL树查询算法与普通搜索二叉树的查询算法相同。类似于二分查找法。

其他操作:

获取最大节点:

public AVLNode<T> findMax(AVLNode<T> t) {
        if (t == null)
            throw new RuntimeException("this AVLNode is Empty");
        if (t.right == null)
            return t;
        return findMax(t.right);
    }

二叉树平衡操作:

public AVLNode<T> balance(AVLNode<T> t) {
        if (t == null)
            return t;
        if (height(t.left) - height(t.right) > 1) {//左插入
            if (height(t.left.left) >= height(t.left.right)) //左左插入  情形1
                t = singleRightRotate(t);//右旋
            else //左右插入           情形2
                t = doubleLeftRightRotate(t);//左右双旋
        }
        if (height(t.right) - height(t.left) > 1) {//右插入
            if (height(t.right.right) >= height(t.right.left)) //右右插入    情形3
                t = singleLeftRotate(t);//左旋
            else //右左插入       情形4
                t = doubleRightLeftRotate(t);//右左双旋
        }
        t.height = height(t.right) > height(t.left) ? height(t.right) + 1 : height(t.left) + 1;
        return t;
    }

返回二叉树深度:

public int maxDeep(AVLNode<T> t) {
        int dl, dr;//记录左右树的深度
        if (t == null)
            return 0;
        else {
            dl = maxDeep(t.left);
            dr = maxDeep(t.right);
        }
        return dl > dr ? dl + 1 : dr + 1;
    }

打印二叉树:

public void infPrintTree(AVLNode<T> t) {
       if (t == null)
           return;
       infPrintTree(t.left);
       System.out.print(t.element + " ");
       infPrintTree(t.right);
   }

旋转:

以某一个节点为轴,它的左子树顺时针旋转,作为新子树的跟,我们称之为右旋。

private AVLNode<T> singleRightRotate(AVLNode<T> t) {
        System.out.println("进行右旋转==============>>>");
        AVLNode<T> r = t.left;
        t.left = r.right;
        r.right = t;
        t.height = height(t.left) > height(t.right) ? height(t.left) + 1 : height(t.right) + 1;
        r.height = height(t.right) > height(r) ? height(t.right) + 1 : height(r) + 1;
        return r;
    }

以某一个节点为轴,它的右子树逆时针旋转,作为新子树的跟,我们称之为左旋。

private AVLNode<T> singleLeftRotate(AVLNode<T> t) {
        System.out.println("进行左旋转==============>>>");
        AVLNode<T> r = t.right;
        t.right = r.left;
        r.left = t;
        t.height = height(t.left) > height(t.right) ? height(t.left) + 1 : height(t.right) + 1;
        r.height = height(t.left) > height(r) ? height(t.left) + 1 : height(r) + 1;
        return r;
    }

对于AVL树种也有可能出现一次单旋无法平衡的情况,就需要进行两次旋转操作,也可称为双旋操作,分为:

左右双旋:先左旋在右旋

private AVLNode<T> doubleLeftRightRotate(AVLNode<T> t) {
        System.out.println("进行左右双旋==============>>>");
        t.left = singleLeftRotate(t.left);
        return singleRightRotate(t);
    }

右左双旋:先右旋在左旋

private AVLNode<T> doubleRightLeftRotate(AVLNode<T> t) {
        System.out.println("进行右左双旋==============>>>");
        t.right = singleRightRotate(t.right);
        return singleLeftRotate(t);
    }

对于左右旋转的理解可见下图:图片来源自网络

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部