AVL-平衡二叉查找树-详解及Java插入示例

由于AVL删除比较复杂,下面不进行详解。仅对AVL概念及插入进行详解

详解

定义

每个节点的左右子树的高度 最多 差1的二叉查找树。其中空树的高度为-1,单节点的高度为0。

性质

高度n的最少节点数为:a(n) = a(n - 1) + a(n - 2) + 1
a0 = 1
a1 = 2
a2 = 4
a3 = 7
a4 = 12
a5 = 20
a6 = 33
a7 = 54
a8 = 88
a9 = 143
n <= 1.44log(a(n) + 2) - 1.328。(证明略,参考《数据结构与算法分析-C语言描述》P80)

插入

分析及效果图演示

会存在下面四种不平衡情况

  • 1)a的左儿子的左子树进行一次插入;
  • 2)a的右儿子的右子树进行一次插入;
  • 3)a的左儿子的右子树进行一次插入;
  • 4)a的右儿子的左子树进行一次插入;
左左-左单旋转

下面k2节点处不符合条件,左向右单旋转入下下图所示,即k1变为根节点,k1的右儿子Y变为k2的左儿子

k2
k1
Z
X
Y
X1
X2
k1
X
k2
Y
Z
X1
X2
右右-右单旋转

下面k1节点处不符合条件,右向左单旋转入下下图所示,即k2变为根节点,k2的左儿子Y变为k1的右儿子

k1
X
k2
Y
Z
Z1
Z2
k2
k1
Z
X
Y
Z1
Z2
左右双旋转

下图再k3处不符合条件,即需要对K3->k1->k2(路径先左后右),则可以先k1和k2进行右单旋转、再k2和k3进行左单旋转实现再平衡,如下下图、下下下图所示。

k3
k1
D
A
k2
B
C
k3
k2
D
k1
A
B
C
k3
C
D
k2
k1
A
B
右左双旋转

同理,先右单旋转、再左单旋转即可平衡

k1
A
k3
k2
D
B
C
k1
A
k2
B
k3
C
D
k3
C
D
k2
k1
A
B

Java例子

AvlNode.java

package com.ydfind.datastructure.avl;

import lombok.Data;

import java.util.Objects;

/**
 * 树节点 对象
 * @author ydifnd
 * @date 2019.10.30
 */
@Data
public class AvlNode {

    private Integer data;
    private AvlNode parent;
    private AvlNode left;
    private AvlNode right;

    public AvlNode(Integer data){
        this.data = data;
    }

    public void setChild(int left, int right){
        this.left = new AvlNode(left);
        this.right = new AvlNode(right);
    }


    public AvlNode insert(AvlNode node){
        int data = node.getData();
        AvlNode result = this;
        // 暂时不支持等于的情况
        if(data < this.data){
            this.left = Objects.isNull(left) ? node : this.left.insert(node);
            if(calcHeight(left) - calcHeight(right) == 2){
                if(data < left.data){
                    result = singleRotateWithLeft(this);
                }else {
                    result = doubleRotate(this, true);
                }
            }
        }else if(data > this.data){
            this.right = Objects.isNull(right) ? node : this.right.insert(node);
            if(calcHeight(right) - calcHeight(left) == 2){
                if(data > right.data){
                    result = singleRotateWithRight(this);
                }else {
                    result = doubleRotate(this, false);
                }
            }
        }
        return result;
    }

    /**
     * 计算高度 单节点为0
     * @return 高度
     */
    public static int calcHeight(AvlNode node){
        // 无节点为-1
        if(Objects.isNull(node) || Objects.isNull(node.data)){
            return -1;
        }
        int leftHeight = Objects.nonNull(node.left) ? calcHeight(node.left) : -1;
        int rightHeight = Objects.nonNull(node.right) ? calcHeight(node.right) : -1;
        int height = leftHeight > rightHeight ? 1 + leftHeight : 1 + rightHeight;
        return height;
    }

    /**
     * 单选择:左往右
     * @param k2
     * @return 返回旋转后的树
     */
    public static AvlNode singleRotateWithLeft(AvlNode k2){
        // k2.left修改。k1的右节点 变为 k2的左节点
        AvlNode k1 = k2.left;
        k2.left = k1.right;
        if(Objects.nonNull(k2.left)) {
            k2.left.parent = k2.left;
        }
        // k1作为新的根节点
        k1.right = k2;
        k1.parent = k2.parent;
        k2.parent = k1;
        return k1;
    }

    /**
     * 单选择:右往左
     * @param k1
     * @return 返回旋转后的树
     */
    public static AvlNode singleRotateWithRight(AvlNode k1){
        AvlNode k2 = k1.right;
        k1.right = k2.left;
        if(Objects.nonNull(k1.right)) {
            k1.right.parent = k2;
        }
        // k2作为新根节点
        k2.left = k1;
        k2.parent = k1.parent;
        k1.parent = k2;
        return k2;
    }

    /**
     * isLeft=true,先下面节点左旋转,再上面节点右旋转;否则相反
     * @param k3
     * @param isLeft 是否是左分支
     * @return
     */
    public static AvlNode doubleRotate(AvlNode k3, boolean isLeft){
        if(isLeft) {
            k3.left = singleRotateWithRight(k3.getLeft());
            k3.left.parent = k3;

            return singleRotateWithLeft(k3);
        }else{
            k3.right = singleRotateWithLeft(k3.getRight());
            k3.right.parent = k3;

            return singleRotateWithRight(k3);
        }
    }

}

测试

代码
package com.ydfind.datastructure.avl;

import org.junit.Test;

import java.util.Objects;

/**
 * avl树测试
 * @author ydifnd
 * @date 2019.10.30
 */
public class AvlNodeTest {


    /**
     * 树的中序遍历
     * @param node
     */
    public void printTree(AvlNode node){
        System.out.print(node.getData());
        if(Objects.nonNull(node.getLeft()) || Objects.nonNull(node.getRight())) {
            System.out.print("(");
            if (Objects.nonNull(node.getLeft())) {
                printTree(node.getLeft());
            }
            System.out.print(",");
            if (Objects.nonNull(node.getRight())) {
                printTree(node.getRight());
            }
            System.out.print(")");
        }
    }

    /**
     * 左右子节点和父节点关联
     * @param node
     */
    public void processParent(AvlNode node){
        if(Objects.nonNull(node.getLeft())){
            node.getLeft().setParent(node);
            processParent(node.getLeft());
        }
        if(Objects.nonNull(node.getRight())){
            node.getRight().setParent(node);
            processParent(node.getRight());
        }
    }

    /***************************************左单旋转***************************/
    /**
     * 左单旋转:加入6
     *             5
     *         /     \
     *       2         8
     *      /  \      /
     *    1     4    7
     *        /     /
     *      3     6
     *    结果:
     *             5
     *         /     \
     *       2         7
     *      /  \      /  \
     *    1     4    6     8
     *        /
     *      3
     */
    @Test
    public void testAvlNodeLeftSingle(){
        // 左单旋转:插入和直接单旋转比较
        System.out.println("\n左-单旋转1");
        AvlNode root = createLeftSingleData();
        printTree(root);
        System.out.print("\n");
        root.insert(new AvlNode(6));
        printTree(root);

        System.out.println("\n左-单旋转2");
        root = createLeftSingleData();
        printTree(root);
        System.out.print("\n");
        root.getRight().getLeft().setLeft(new AvlNode(6));
        // 对8节点进行左单旋转
        root.setRight(AvlNode.singleRotateWithLeft(root.getRight()));
        printTree(root);
        System.out.print("\n");
    }

    public AvlNode createLeftSingleData(){
        AvlNode root = new AvlNode(5);
        root.setChild(2, 8);
        root.getLeft().setChild(1, 4);
        root.getLeft().getRight().setLeft(new AvlNode(3));

        root.getRight().setLeft(new AvlNode(7));
        processParent(root);
        return root;
    }

    /**************************************右单旋转***************************/
    /**
     * 右单旋转:加入5
     *        2
     *      /   \
     *     1      3
     *              \
     *                4
     *                  \
     *                    5
     *    结果:
     *       2
     *     /   \
     *   1      4
     *        /  \
     *       3     5
     */
    @Test
    public void testAvlNodeRightSingle(){
        // 左单旋转:插入和直接单旋转比较
        System.out.println("\n右-单旋转1");
        AvlNode root = createRightSingleData();
        printTree(root);
        System.out.print("\n");
        root.insert(new AvlNode(5));
        printTree(root);

        System.out.println("\n右-单旋转2");
        root = createRightSingleData();
        printTree(root);
        System.out.print("\n");
        root.getRight().getRight().setRight(new AvlNode(5));
        root.setRight(AvlNode.singleRotateWithRight(root.getRight()));
        printTree(root);
        System.out.print("\n");
    }

    public AvlNode createRightSingleData(){
        AvlNode root = new AvlNode(2);
        root.setChild(1, 3);

        root.getRight().setRight(new AvlNode(4));
        processParent(root);
        return root;
    }

    /**************************************右双旋转***************************/
    /**
     * 右单旋转:加入14时,在6、15、7触发右双旋转
     *             4
     *         /     \
     *       2         6
     *      /  \      /  \
     *    1     3    5    15
     *                   /   \
     *                 7      16
     *                  \
     *                   14
     *    结果:
     *             4
     *         /     \
     *       2         7
     *      /  \      /  \
     *    1     3    6    15
     *              /    /   \
     *             5   14     16
     */
    @Test
    public void testAvlNodeLeftDouble(){
        // 左单旋转:插入和直接单旋转比较
        System.out.println("\n右双旋转1");
        AvlNode root = createLeftDoubleData();
        printTree(root);
        System.out.print("\n");
        root.insert(new AvlNode(14));
        printTree(root);

        System.out.println("\n右双旋转2");
        root = createLeftDoubleData();
        printTree(root);
        System.out.print("\n");
        root.getRight().getRight().getLeft().setRight(new AvlNode(14));
        root.setRight(AvlNode.doubleRotate(root.getRight(), false));
        printTree(root);
        System.out.print("\n");
    }

    public AvlNode createLeftDoubleData(){
        AvlNode root = new AvlNode(4);
        root.setChild(2, 6);
        root.getLeft().setChild(1, 3);

        root.getRight().setChild(5, 15);
        root.getRight().getRight().setChild(7, 16);

        processParent(root);
        return root;
    }

}

左单旋转-测试代码图示

下图插入6后,不再平衡

1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
其他图示

请参考测试用例函数上面的解释

参考

《数据结构与算法分析–C语言分析》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值