数据结构(Java)-树-平衡二叉树

一、问题引入

你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在、

上边BST 存在的问题分析:

1) 左子树全部为空,从形式上看,更像一个单链表 .
2) 插入速度没有影响
3) 查询速度明显降低 ( 因为需要依次比较 ), 不能发挥 BST 的优势,因为每次还需要比较左子树,其查询速度比 单链表还慢
4) 解决方案 - 平衡二叉树(AVL)

 二、平衡二叉树的概念

1) 平衡二叉树也叫 平衡二叉搜索树 Self-balancing binary search tree )又被称为 AVL 树, 可以 保证查询效率较高
2) 具有以下 特点 :它是一棵空树或它的左右 两个子树 高度差 的绝对值 不超过1 ,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有 红黑树 AVL 替罪羊树 Treap 伸展树 等。

        上图中1、2都是平衡二叉树,而3不是。

        那我们如何解决不平衡问题呢?采用左旋转、右旋转、双旋转(左+右或右+左)的方式

三、平衡二叉树的操作

1.查询树、左右子树的高度

        利用递归的思想,非常巧妙

  //1.获取子树高度
        public int Height(Node node) {
            return Math.max(node.left == null ? 0 : Height(node.left), node.right == null ? 0 : Height(node.right)) + 1;
        }

        //2.获取左子树高度
        public int leftHeight(Node node) {
            if (node.left == null) return 0;
            else return Height(node.left);
        }

        //3.获取右子树高度
        public int rightHeight(Node node) {
            if (node.right == null) return 0;
            else return Height(node.right);
        }

2.左旋转

思路,以下图为例,以 “4” 节点为基准进行左旋转:

        1.先创建一个新的 “4” 节点 new,新的 “4” 称为“new”,旧的“4”称为“old。

        2.new 左子树设为 old左子树。"4"->"3"

        2.new的右子树设为 old 右子树左子树  "4"->"5"

                至此,新节点的左右子树都设置好了。

        3. old换成其右子节点的值 ”4“=”6“

        4.old的右子树设置为其右子树右子树  "6"-> "7"

        5.old的左子树设置为new "6"->"4"

        至此,旧节点的左右子树都设置好了。

        整个过程相当于把根节点右节点当做根节点,把它向上拎了一下。

        我们创建了一个新的 "4" 节点,它原来的位置被它的右子树 “6” 替换了,而 "6" 原来的位置相当于被废弃掉了,不会再访问到。

//4.左旋转
        public void leftRotate(Node root) {
            Node newNode = new Node(root.value);
            newNode.left = root.left;
            newNode.right = root.right.left;
            root.value = root.right.value;
            root.right = root.right.right;
            root.left = newNode;
        }

        测试:

@Test
    public void testLeftRotate() {
        int[] arr = {4, 3, 6, 5, 7, 8};
        Node root = new Node(4);
        BalanceBinaryTree tree = new BalanceBinaryTree(root);
        for (int i = 1; i < arr.length; i++) tree.add(root, new Node(arr[i]));
        tree.infixOrder(root);
        System.out.println("\n" + "子树高度:" + tree.Height(root) + ",左子树高度:" + tree.leftHeight(root) + ",右子树高度:" + tree.rightHeight(root));
        tree.leftRotate(root);
        System.out.println("\n" + "子树高度:" + tree.Height(root) + ",左子树高度:" + tree.leftHeight(root) + ",右子树高度:" + tree.rightHeight(root));
        tree.infixOrder(root);
    }



3 4 5 6 7 8 
子树高度:4,左子树高度:1,右子树高度:3


3 4 5 6 7 8 
子树高度:3,左子树高度:2,右子树高度:2

 3.右旋转

思路,以下图为例,以 “10” 节点为基准进行右旋转:

        1.先创建一个新的 “10” 节点 new,新的 “10” 称为“new”,旧的“10”称为“old。

        2.new 右子树设为 old右子树。"10"->"12"

        2.new的左子树设为 old 左子树右子树  "10"->"9"

                至此,新节点的左右子树都设置好了。

        3. old换成其左子节点的值 ”10“=”8“

        4.old的左子树设置为其左子树左子树  "8"-> "7"

        5.old的右子树设置为new "8"->"10"

        至此,旧节点的左右子树都设置好了。

        整个过程相当于把根节点左节点当做根节点,把它向上拎了一下。

        我们创建了一个新的 "10" 节点,它原来的位置被它的左子树 “8” 替换了,而 "8" 原来的位置相当于被废弃掉了,不会再访问到。

 

//5.右旋转
        public void rightRotate(Node root) {
            Node newNode = new Node(root.value);
            newNode.left = root.left.right;
            newNode.right = root.right;
            root.value = root.left.value;
            root.left = root.left.left;
            root.right = newNode;
        }

        测试:

 @Test
    public void testRightRotate() {
        int[] arr = {10, 12, 8, 9, 7, 6};
        Node root = new Node(10);
        BalanceBinaryTree tree = new BalanceBinaryTree(root);
        for (int i = 1; i < arr.length; i++) tree.add(root, new Node(arr[i]));
        tree.infixOrder(root);
        System.out.println("\n" + "子树高度:" + tree.Height(root) + ",左子树高度:" + tree.leftHeight(root) + ",右子树高度:" + tree.rightHeight(root));
        tree.rightRotate(root);
        System.out.println("\n" + "子树高度:" + tree.Height(root) + ",左子树高度:" + tree.leftHeight(root) + ",右子树高度:" + tree.rightHeight(root));
        tree.infixOrder(root);
    }



6 7 8 9 10 12 
子树高度:4,左子树高度:3,右子树高度:1


6 7 8 9 10 12 
子树高度:3,左子树高度:2,右子树高度:2

4.双旋转

前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列

        int[] arr = { 10, 11, 7, 6, 8, 9 };  运行原来的代码可以看到,并没有转成 AVL.

        int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL

解决方法:

        1.如果要对 A 进行右旋转,先看下A的左子树右子树高度是否大于A的左子树。如果满足就要先对A的左节点进行左旋转,然后再回过头对A进行右旋转

        1.如果要对 A 进行左旋转,先看下A的右子树左子树高度是否大于A的右子树。如果满足就要先对A的右节点进行右旋转,然后再回过头对A进行左旋转

        这里我想到用递归的方法解决,这样思路比较清晰。双旋转包括了左右旋转的所有情况,具有普适性。我们从根节点递归地进行判断,然后旋转

//6. 自动双旋转(从根往下递归旋转)
        public void autoRotate(Node node) {
            //希望左旋转
            if (rightHeight(node) - leftHeight(node) >= 1) {
                //右儿子出现问题了,它的左子树高度大于右子树高度
                if (node.right != null && leftHeight(node.right) > rightHeight(node.right)) {
                    //递归到右儿子去
                    autoRotate(node.right);
                }
                //如果右儿子没出现问题,直接左旋转
                leftRotate(node);
            }
            //希望右旋转
            if (leftHeight(node) - rightHeight(node) >= 1) {
                //左儿子出现问题了,它的右子树高度大于左子树高度
                if (node.left != null && rightHeight(node.left) > leftHeight(node.left)) {
                    //递归到左儿子去
                    autoRotate(node.left);
                }
                //如果左儿子没出现问题,直接右旋转
                rightRotate(node);
            }
        }
 //7. 自动双向旋转的添加操作
        public void autoRotateAdd(Node node) {
            add(root, node);
            autoRotate(root);
        }

注:add方法是二叉排序树一节中的add方法,直接拿过来用。

        测试:

为了验证普适性,我们各种参数都随机生成

 @Test
    public void testAutoRotateAdd() {
        int j = 30;
        while (j > 0) {
            Random random = new Random();
            int[] arr = new int[random.nextInt(50)+10];
            for (int i = 0; i < arr.length; i++) arr[i] = random.nextInt(100);
            Node root = new Node(arr[0]);
            BalanceBinaryTree tree = new BalanceBinaryTree(root);
            for (int i = 1; i < arr.length; i++) tree.autoRotateAdd(new Node(arr[i]));
            tree.infixOrder(root);
            System.out.println("\n" + "子树高度:" + tree.Height(root) + ",左子树高度:" + tree.leftHeight(root) + ",右子树高度:" + tree.rightHeight(root));
            System.out.println();
            j--;
        }
    }
1 4 5 5 7 10 11 11 11 12 13 16 18 20 24 29 32 34 34 37 38 38 44 48 48 50 50 51 51 52 52 53 57 65 66 69 70 71 73 74 78 79 80 82 87 93 
子树高度:9,左子树高度:7,右子树高度:8

0 0 1 5 5 6 6 7 7 8 8 8 9 13 14 17 25 26 28 32 36 37 40 42 42 42 43 45 47 47 47 47 48 50 50 60 60 60 62 64 64 65 67 68 72 73 75 80 80 83 85 88 91 91 93 95 98 
子树高度:10,左子树高度:9,右子树高度:9

0 1 1 2 7 8 11 12 15 20 21 25 26 27 30 31 35 35 36 38 38 41 42 44 46 52 52 53 58 58 62 63 63 66 69 71 73 74 75 76 76 77 80 80 80 81 81 85 86 87 87 89 93 95 96 96 97 97 99 
子树高度:9,左子树高度:7,右子树高度:8

2 2 3 4 5 7 10 12 16 16 21 22 22 22 24 26 26 26 29 32 35 36 40 41 41 41 42 42 43 44 45 45 46 46 48 48 48 49 52 57 59 59 62 64 71 73 84 87 87 90 91 91 91 92 93 97 97 
子树高度:10,左子树高度:8,右子树高度:9

1 2 3 5 7 10 12 12 13 14 15 15 16 20 24 24 24 27 28 30 38 39 44 46 47 47 48 56 57 58 60 63 67 67 68 70 77 78 79 81 82 82 83 83 84 87 93 93 95 95 95 95 97 99 
子树高度:9,左子树高度:8,右子树高度:8

2 11 19 19 20 20 26 27 27 28 28 31 37 38 39 41 46 46 56 58 60 64 77 82 85 90 96 98 
子树高度:7,左子树高度:6,右子树高度:6

8 11 16 17 19 27 29 29 33 33 41 44 49 50 52 53 55 58 59 63 66 68 68 72 75 79 80 81 83 84 85 87 94 98 
子树高度:7,左子树高度:5,右子树高度:6

3 6 10 10 10 16 18 20 21 26 27 29 32 45 47 48 48 52 54 55 55 57 61 69 75 77 81 89 97 99 
子树高度:7,左子树高度:5,右子树高度:6

0 1 4 5 7 9 9 12 12 12 13 14 15 17 18 21 24 24 24 27 33 35 36 38 43 45 46 47 50 57 58 58 58 62 62 66 67 71 74 77 77 82 82 83 83 85 86 88 88 88 89 91 95 96 97 
子树高度:9,左子树高度:8,右子树高度:8

7 12 12 15 23 23 24 27 28 28 29 31 32 37 38 39 43 43 49 51 53 55 58 61 61 61 63 64 67 68 74 74 76 77 77 79 79 83 95 99 
子树高度:9,左子树高度:8,右子树高度:8

5 6 21 24 26 30 31 42 44 66 90 91 94 95 99 99 
子树高度:6,左子树高度:4,右子树高度:5

0 5 7 8 12 12 17 17 21 21 22 30 42 46 46 49 50 61 63 64 74 75 78 81 89 90 93 93 95 
子树高度:7,左子树高度:5,右子树高度:6

16 22 24 24 27 33 46 51 54 58 60 74 80 81 86 87 88 94 94 
子树高度:6,左子树高度:5,右子树高度:5

2 9 11 19 19 20 21 23 24 24 24 27 31 33 37 39 40 43 47 48 51 53 54 55 56 56 56 57 57 59 59 60 61 64 67 70 72 72 79 80 82 83 84 84 87 88 92 92 92 99 99 
子树高度:9,左子树高度:7,右子树高度:8

0 7 8 17 18 21 25 26 27 31 32 36 43 44 44 48 50 52 52 53 54 56 57 61 61 72 74 76 77 79 91 91 91 91 93 96 98 
子树高度:10,左子树高度:9,右子树高度:9

2 2 2 4 8 9 11 12 16 23 28 31 31 31 32 36 37 37 39 42 45 49 53 54 55 58 58 59 59 64 65 67 67 69 69 71 74 76 77 78 79 79 80 80 81 83 84 86 88 88 90 92 93 93 94 95 99 99 
子树高度:9,左子树高度:8,右子树高度:8

4 6 6 7 9 12 15 17 18 21 22 23 24 27 27 28 28 31 32 33 33 33 34 36 36 38 38 44 45 47 49 54 54 55 55 61 62 64 64 68 75 79 81 82 86 87 87 90 90 91 91 93 97 97 99 
子树高度:9,左子树高度:8,右子树高度:8

0 2 2 4 11 16 16 21 23 30 34 40 48 60 65 72 80 81 90 92 96 98 99 
子树高度:7,左子树高度:5,右子树高度:6

3 8 10 11 14 14 14 19 20 20 23 26 27 29 30 31 31 40 41 46 46 48 50 51 51 53 53 58 63 68 70 73 73 73 76 78 79 79 81 83 84 84 86 90 91 91 92 93 96 99 
子树高度:9,左子树高度:7,右子树高度:8

1 2 3 4 11 11 12 16 19 20 31 37 38 38 45 45 46 46 48 49 54 55 57 58 61 64 65 68 68 69 71 72 73 77 78 79 80 81 83 86 91 91 92 92 93 97 99 
子树高度:9,左子树高度:8,右子树高度:8

0 2 2 10 10 11 16 17 19 20 26 28 28 36 41 46 48 50 54 70 72 74 75 81 88 88 92 93 94 95 95 96 99 
子树高度:7,左子树高度:6,右子树高度:6

17 23 29 39 39 40 49 57 63 66 73 74 75 82 84 84 90 
子树高度:6,左子树高度:5,右子树高度:5

3 4 24 33 36 37 48 48 86 99 
子树高度:4,左子树高度:3,右子树高度:3

5 18 21 26 28 35 36 36 39 40 45 46 47 49 51 53 58 61 65 67 68 74 79 89 92 97 
子树高度:7,左子树高度:5,右子树高度:6

11 17 18 24 27 35 38 40 46 67 67 87 95 
子树高度:6,左子树高度:5,右子树高度:5

0 1 2 3 3 6 7 7 8 12 16 16 17 19 22 23 24 25 25 25 27 28 29 30 33 34 34 44 53 53 57 58 58 59 61 62 68 70 71 73 77 77 77 80 87 87 88 89 89 94 94 95 96 97 97 99 99 99 
子树高度:9,左子树高度:7,右子树高度:8

8 8 11 19 27 36 40 41 44 57 62 69 73 76 77 81 93 
子树高度:5,左子树高度:4,右子树高度:4

2 3 9 18 18 21 28 33 39 43 49 53 76 79 80 80 83 92 99 
子树高度:6,左子树高度:4,右子树高度:5

5 10 19 22 26 39 44 47 52 66 73 74 83 97 
子树高度:5,左子树高度:4,右子树高度:4

2 3 3 9 10 16 16 17 22 23 26 38 41 42 42 54 58 58 60 70 70 70 71 72 73 77 81 81 82 
子树高度:9,左子树高度:8,右子树高度:8

四、总结

        平衡二叉树AVL是对二叉排序树BST的优化,主要就是利用了左旋转、右旋转的策略实现左右深度不大于1的效果。结合左右旋转的方法可以实现双旋转,有很强的普适性。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值