• AVL树(Java实现)


     AVL树基本介绍

    AVL树是一种自平衡的二叉查找树,在AVL树中任何节点的两个子树的高度差不能超过1。就是相当于在二叉搜索树的基础上,在插入和删除时进行了平衡处理。

    不平衡的四种情况

    LL:结构介绍

    看如下图,假设最初只有k1, k2, k3, y, z 五个结点,这时该树两边的高度分别为3 和 2,相差为1,满足AVL平衡的概念。

    随后插入了结点 x ,导致了不平衡。k1.left.left 有了子树,导致了不平衡。所以是LL结构。

    (这个 x 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

       

    LL:处理

    调整的时候发现,k3 和 x 的相对连接关系一直没变,所以x是k3的左孩子还是右孩子无所谓了,都是LL处理方式。x 如果是 k3 的左孩子,处理方式和步骤一模一样。

    ---> ---> --->

    --->--->

    LL:代码

        /**
         * @param k1 k1结点的左右两边的高度差2
         * @return 返回LL单转后的新根k2
         */
        private Node leftLeftRotation(Node k1) {
            Node k2 = k1.left;
    
            k1.left = k2.right;
            k2.right = k1;
    
            k1.height = max(height(k1.left), height(k1.right)) + 1;
            k2.height = max(height(k2.left), k1.height) + 1;
    
            return k2;
        }  

    RR:结构介绍

    看如下图,假设最初只有k1, k2, k3, x, y 五个结点,这时该树两边的高度分别为2 和 3,相差为1,满足AVL平衡的概念。

    随后插入了结点 z ,导致了不平衡。是 k1.right.right 有了子树,导致了不平衡。所以是RR结构。

    (这个 z 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

      

    RR:处理

     调整的时候发现,k3 和 z 一直没动,所以 z 是 k3 的左孩子还是右孩子无所谓了,都是 RR 处理方式。z 如果是 k3 的左孩子,处理方式和步骤一模一样。

    --->--->--->

    --->--->

    RR:代码

        /**
         * @param k1 k1结点的左右两边的高度差2
         * @return 返回RR单转后的新根k2
         */
        private Node rightRightRotation(Node k1) {
            Node k2 = k1.right;
    
            k1.right = k2.left;
            k2.left = k1;
    
            k1.height = max(height(k1.left), height(k1.right)) + 1;
            k2.height = max(k1.height, height(k2.right)) + 1;
    
            return k2;
        }  

    LR:结构介绍

    看如下图,假设最初只有k1, k2, k3, x, z 五个结点,这时该树两边的高度分别为3 和 2,相差为1,满足AVL平衡的概念。

    随后插入了结点 y ,导致了不平衡。是 k1.left.right 有了子树,导致了不平衡。所以是LR结构。

    (这个 y 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

     

    LR:处理

    为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。

    现在是LR结构。

    先不看k1 和 z 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成RR结构来进行处理。即先处理这个'R'

    --->---> 

    如上面最后一张图,经过了RR单转之后,变成了LL结构。对k1为根的树其进行LL单转。如下所示:

    --->--->

    本次我只演示了当 y 是k3的左孩子时的情况。如果y插入时,是k3的右孩子,那其实就是图片中null结点的地方。转换方式是一模一样。

    LR:代码

    发现LL 和 RR写好以后,LR虽然复杂,但直接调用这两个方法就行了。很短的。

        /**
         * @param k1 k1结点的左右两边的高度差2
         * @return 返回LR单转后的新根k2
         */
        private Node leftRightRotation(Node k1) {
            k1.left = rightRightRotation(k1.left);
            return leftLeftRotation(k1);
        }  

    RL:结构介绍

    看如下图,假设最初只有k1, k2, k3, x, z 五个结点,这时该树两边的高度分别为2 和 3,相差为1,满足AVL平衡的概念。

    随后插入了结点 y ,导致了不平衡。是 k1.right.left 有了子树,导致了不平衡。所以是RL结构。

    (这个 y 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

     

    RL:处理

    为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。

     

    现在是RL结构。

    先不看k1 和 x 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成LL结构来进行处理。即先处理这个'L'

    ->->->

    如上面最后一张图,经过了RR单转之后,变成了RR结构。对k1为根的树其进行RR单转。如下所示:

       

     本次我只演示了当 y 是k3的左孩子时的情况。如果y插入时,是k3的右孩子,那其实就是图片中null结点的地方。转换方式是一模一样。

    RL:代码

        /**
         * @param k1 k1结点的左右两边的高度差2
         * @return 返回RL单转后的新根k2
         */
        private Node rightLeftRotation(Node k1) {
            k1.right = leftLeftRotation(k1.right);
            return rightRightRotation(k1);
        }  

    具体讲解插入导致的不平衡

    咱们假设AVL树的初始状态如下,

     随后插入了一个新的结点7。7与根进行比较,发现7<50,所以去左子树查找。

    7继续进行比较,7<30,所以继续去左子树查找。

    重复这些步骤,在代码里是用递归处理的。直到找到7为止,或者遍历到叶子节点了也没找到7为止。

    没找到7...因为7比15小,所以7插入在15左侧。这时以k1为根的树不平衡了!!!

    问:我能用眼睛看出来,但程序怎么算呢?   答:height(k1.left) - height(k1.right) == 2 了。

    问:为什么是左子树高度 减 右子树高度?   答:因为...之前是平衡的,在左侧插入后就不平衡了。肯定是左侧大啊。

    问:嗯,左子树高度比右子树大说明啥?   答:说明...肯定是 k1 左边。也就是LL结构或者是LR结构,第一个字母 ‘L’ 已经确定了。

    问:那剩下的你怎么确定是LL还是LR ?  答:很简单,新节点比k2小,那就是LL;新节点比k2大,那就是LR。继续往下看图:

    ---确定了LL结构---> 

    问:LL结构挺好理解,判断LR会不会很麻烦啊? 答:方法一模一样...没多没少...咱们假设一开始插入的不是7,而是35,那么如下图所示:

    ---确定LR--->

    问:为什么不介绍RR和RL了? 答:LL、LR都会了,RR、RL其实就是一码事了。不再重复演示了.....(画图好累....)

    具体讲解删除导致的不平衡

    讲删除导致不平衡之前,先讲讲到底怎么在AVL树中删除一个结点。

    假设删除前的树如下图所示,想要删除 90 结点:

    问:怎么删除呢? 答:先从树根定位到 90 结点。

    问:怎么定位呢?  答:利用递归,与当前子树根进行比较。小,去左边找;大,去右边找;等于,就找到了。

    问:找到之后就可以直接删了吗? 答:还得考虑要删除的那个结点是否还有孩子。分为如下三种情况:

    情况1:   90结点没有左孩子

    没有左孩子的话,把70结点的right 连上 90结点的right。没有左孩子不代表一定有右孩子。如果90的right是null的话,那就把这个null赋给70的right就好了。

    ->->

     情况2:   90结点没有右孩子

    没有右孩子的话,把 70结点的right 连上 90结点的left。

    ->->

    情况3:   90结点既有左孩子又有右孩子

    如果要删除的 90 结点情况如下,那么需要在删除前找到 90 结点的 前驱结点 或者 后继结点。如下面的第一张图。

    问:找 前驱结点 / 后继结点 干嘛呢? 答:90的前驱和后继结点是最接近90的两个结点。用他们俩替换掉90,就相当于删掉了90。如下面的第二张图。

    问:第二张图我看完了,你选用90的后继结点93替换了90,之后树中不就有两个93了吗? 答:删掉原来的93就好了

    问:原来的93怎么删? 答:去新的93的右子树里找原来的93,并且删除。咱们现在介绍的是情况3(被删除的结点有左右孩子),而且93是后继结点,肯定不会有左孩子,所以删除原来的93的问题,就回到了情况1。

    一句概括就是:在新93结点的右子树中删除原93。(见下面第3张图)

    问:你图中演示的是后继结点93来替换90,那前驱结点呢? 答:刚才说了后继结点肯定没有左孩子,而前驱结点肯定没有右孩子。用前驱80结点来替换90之后,再去删除原来的80,而这个80肯定没有右孩子,所以就回到了情况2。

    一句话概括就是:如果用前驱80替换90,再删除原来的80,就回到了情况2;如果用后继93替换90,再删除原来的93,就回到了情况1。

    看起来选前驱和选后继没什么两样,但是,如果90结点的右子树深,最好用后继来替换;如果90结点的左子树深,最好用前驱来替换。

    ->->

    问:前驱和后继怎么求呢? 答: 首先要会求树中的最大、最小结点。从根开始向左遍历,到头了,那么这个最左边的叶子节点就是最小值。同理,一直向右遍历就是最大值。

    问:那么最大最小值跟前驱后继什么关系呢? 答:咱们以情况3的第一张图片为例。找50的前驱怎么找呢,就是50结点的左子树中的最大值。后继结点呢,就是50结点的右子树中的最小值。

    问:还是不懂,给我看看代码? 

    答:

        private Node minNode(Node node) {
            if (node == null) {
                return null;
            } else if (node.left == null) {
                return node;
            } else {
                return minNode(node.left);
            }
        }
    --------------------------------------------------------
    Node successor = minNode(node.right);//找node的后继结点successor
    

      

    如何删除一个结点已经讲完了。

    下面看看删除一个结点后都会遇到什么不平衡的情况。

    假设删除前的树如下图所示,想要删除 90 结点:

    删除值为90的结点后,高度差为2,不满足AVL定义。

     

    因为是删除了50 (k1) 右子树中的结点后导致的不平衡,所以肯定是左子树太深导致了不平衡。

    第一个字母‘L’已经确定,所以是 LL 和 LR 结构之一。

    需要判断 k2 的左右两子树,左边深,那就是LL;右边深,那就是LR。

     ---确定LL结构--->

     如果k2的左右两子树高度相等...那就LL/LR随意了...主要目的就是处理掉高的那部分。

    AVL树完整代码

    public class AVLTree<Key extends Comparable<? super Key>, Value> {
        private class Node {
            Key key;//键,相当于词典里的单词
            Value value;//值,相当于词典里的单词解释
            int height;//结点的高度
            Node left;
            Node right;
    
            public Node(Key key, Value value) {
                this.key = key;
                this.value = value;
                this.left = null;
                this.right = null;
                int height = 1;
            }
        }
    
        private Node root;
    
        public AVLTree() {
            root = null;
        }
    
        private int height(Node node) {
            if (node != null) {
                return node.height;
            }
            return 0;
        }
    
        public int height() {
            return height(root);
        }
    
        private int max(int a, int b) {
            return a > b ? a : b;
        }
    
        private void replaceNode(Node src, Node tar) {
            tar.key = src.key;
            tar.value = src.value;
        }
    
        private void preOrder(Node node) {
            if (node != null) {
                System.out.println(node.key);
                preOrder(node.left);
                preOrder(node.right);
            }
        }
    
        public void preOrder() {
            preOrder(root);
        }
    
        private void inOrder(Node node) {
            if (node != null) {
                inOrder(node.left);
                System.out.println(node.key);
                inOrder(node.right);
            }
        }
    
        public void inOrder() {
            inOrder(root);
        }
    
        public void postOrder(Node node) {
            if (node != null) {
                postOrder(node.left);
                postOrder(node.right);
                System.out.println(node.key);
            }
        }
    
        public void postOrder() {
            postOrder(root);
        }
    
        private Node search(Node node, Key key) {
            if (node == null) {
                return null;
            } else if (key.compareTo(node.key) == 0) {
                return node;
            } else if (key.compareTo(node.key) < 0) {
                return search(node.left, key);
            } else {//key.compareTo(node.key) > 0
                return search(node.right, key);
            }
        }
    
        public Node search(Key key) {
            return search(root, key);
        }
    
        private Node minNode(Node node) {
            if (node == null) {
                return null;
            } else if (node.left == null) {
                return node;
            } else {
                return minNode(node.left);
            }
        }
    
        public Node minNode() {
            return minNode(root);
        }
    
        private Node maxNode(Node node) {
            if (node == null) {
                return null;
            } else if (node.right == null) {
                return node;
            } else {
                return maxNode(node.right);
            }
        }
    
        public Node maxNode() {
            return maxNode(root);
        }
    
        // 对如下的LL情况
        //
        //         k1                 k2
        //        /  \               /  \
        //       k2   z    LL单转   x    k1
        //      /  \       ----\   /    / \
        //     x    y      ----/  o    y   z
        //    //    /      k1右旋
        //   o
        //
        //   或
        //
        //         k1                 k2
        //        /  \               /  \
        //       k2   z    LL单转    x   k1
        //      /  \       ----\     \  / \
        //     x    y      ----/      o y  z
        //      \          k1右旋
        //       o
        //
        private Node leftLeftRotation(Node k1) {
            Node k2 = k1.left; //k2是k1的左子树
    
            k1.left = k2.right;//k2的右子树 变为 k1 的左子树
            k2.right = k1; //k1变为k2的右子树
    
            k1.height = max(height(k1.left), height(k1.right)) + 1;//计算k1的高度
            k2.height = max(height(k2.left), k1.height) + 1;//计算k2的高度
    
            return k2;//返回新的根k2
        }
    
    
        // 对如下的RR情况
        //
        //         k1                      k2
        //        /  \                    /  \
        //       x    k2      RR单转     k1   k3
        //           / \      ----\     / \    \
        //          y   k3    ----/    x   y    z
        //               \    k1左旋
        //                z
        //
        //   或
        //
        //         k1                      k2
        //        /  \                    /  \
        //       x    k2      RR单转       k1   k3
        //           / \      ----\     / \   /
        //          y  k3     ----/    x   y z
        //             /      k1左旋
        //            z
        //
        public Node rightRightRotation(Node k1) {
            Node k2 = k1.right;
    
            k1.right = k2.left;
            k2.left = k1;
    
            k1.height = max(height(k1.left), height(k1.right)) + 1;
            k2.height = max(k1.height, height(k2.right)) + 1;
    
            return k2;
        }
    
        // 对如下的LR情况
        //      k1                k1                k3
        //     /  \              /  \              /  \
        //    k2   z  RR单转    k3   z   LL单转    k2  k1
        //   /  \     -----\   / \      -----\   / \  / \
        //  w   k3    -----/  k2  y     -----/  w  x y   z
        //     /  \   k2左旋  / \        k1右旋
        //    x    y         w  x
        //
        public Node leftRightRotation(Node k1) {
            k1.left = rightRightRotation(k1.left);
            return leftLeftRotation(k1);
        }
    
        // 对如下的RL情况
        //    k1                k1                  k3
        //   /  \     LL单转    / \      RR单旋     /  \
        //  w   k2    -----\   w  k3    -----\    k1  k2
        //      / \   -----/     / \    -----/   / \  / \
        //     k3  z  k2右旋     x  k2   k1左旋  w   x y  z
        //    / \                  / \
        //   x   y                y   z
        //
        public Node rightLeftRotation(Node k1) {
            k1.right = leftLeftRotation(k1.right);
            return rightRightRotation(k1);
        }
    
        //插入
        private Node insert(Node node, Key key, Value value) {
            if (node == null) return new Node(key, value);
    
            if (key.compareTo(node.key) == 0) {//如果key相同则更新该节点
    
                node.value = value;
    
            } else if (key.compareTo(node.key) < 0) {//如果key比当前根小,则去左子树找。即一步Left
    
                node.left = insert(node.left, key, value);
                if (height(node.left) - height(node.right) == 2) {//插在左边所以肯定是左-右,高度差2表示已经不平衡
                    if (key.compareTo(node.left.key) < 0) {// 又一步Left,所以是LeftLeft
                        node = leftLeftRotation(node);
                    } else { //一步Right,所以是LeftRight
                        node = leftRightRotation(node);
                    }
                }
    
            } else {   // node.key < key,那么去右子树找.即一步Right
    
                node.right = insert(node.right, key, value);
                if (height(node.right) - height(node.left) == 2) {//插在右边所以肯定是右-左,高度差2表示已经不平衡
                    if (key.compareTo(node.right.key) > 0) {//又一步Right,所以是RightRight
                        node = rightRightRotation(node);
                    } else {//一步Left,所以是RightLeft
                        node = rightLeftRotation(node);
                    }
                }
    
            }
    
            node.height = max(height(node.left), height(node.right)) + 1;
            return node;
        }
    
        public void insert(Key key, Value value) {
            this.root = insert(this.root, key, value);
        }
    
        //删除
    
        /**
         * @param node   当前子树根节点
         * @param target 要删除的结点
         * @return 删除后的新的子树根
         */
        public Node remove(Node node, Node target) {
            if (node == null || target == null) return node;
    
            if (target.key.compareTo(node.key) < 0) {//待删除key的比根的key小,那么继续在左子树查找
    
                node.left = remove(node.left, target);
                if (height(node.right) - height(node.left) == 2) {//如果在删除后失去平衡
                    if (height(node.right.left) <= height(node.right.right)) {
                        node = rightRightRotation(node);
                    } else {
                        node = rightLeftRotation(node);
                    }
                }
    
            } else if (node.key.compareTo(target.key) < 0) {//待删除key的比根的key大,那么继续在右子树查找
    
                node.right = remove(node.right, target);
                if (height(node.left) - height(node.right) == 2) {
                    if (height(node.left.right) <= height(node.left.left)) {
                        node = leftLeftRotation(node);
                    } else {
                        node = rightRightRotation(node);
                    }
                }
    
            } else { // node.key == target.key
                if (node.left == null) { // 如果node的左子树为空,那么删除node后,新的根就是node.right
                    return node.right;
                } else if (node.right == null) {// 如果node的右子树为空,那么删除node后,新的根就是node.left
                    return node.left;
                } else { // 如果node既有左子树,又有右子树
    
                    if (height(node.left) > height(node.right)) {//如果左子树比右子树深
                        Node predecessor = maxNode(node.left);//找node的前继结点predecessor
                        replaceNode(predecessor, node);//predecessor替换node
                        node.left = remove(node.left, predecessor);//再把原来的predecessor删掉
                    } else {//如果右子树比左子树深(一样深的话无所谓了)
                        Node successor = minNode(node.right);//找node的后继结点successor
                        replaceNode(successor, node);//successor替换node
                        node.right = remove(node.right, successor);//再把原来的successor删掉
                    }
    
                }
            }
            return node;
        }
    
        public void remove(Key key) {
            Node z;
            if ((z = search(root, key)) != null)
                root = remove(root, z);
        }
    
        private void destroy(Node node) {
            if (node == null)
                return;
    
            if (node.left != null)
                destroy(node.left);
            if (node.right != null)
                destroy(node.right);
    
            node = null;
        }
    
        public void destroy() {
            destroy(root);
            System.out.println("销毁完毕");
        }
    
        private void print(Node tree, Key key, String pos) {
            if (tree != null) {
                if (pos.equals(""))    // tree是根节点
                    System.out.printf("%2d is root\n", tree.key);
                else                // tree是分支节点
                    System.out.printf("%2d is %2d's %6s child\n", tree.key, key, pos);
    
                print(tree.left, tree.key, "left");
                print(tree.right, tree.key, "right");
            }
        }
    
        public void print() {
            if (root != null) print(root, root.key, "");
        }
    
        //***************************************************************
        private static int arr[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9};
    
        public static void main(String[] args) {
            int i;
            AVLTree<Integer, Integer> tree = new AVLTree<>();
    
            System.out.printf("*******依次添加: ");
            for (i = 0; i < arr.length; i++) {
                System.out.printf("%d ", arr[i]);
                tree.insert(arr[i], arr[i]);
            }
            System.out.println();
    
    
            System.out.print("*******前序遍历: ");
            tree.preOrder();
            System.out.println();
    
    
            System.out.print("*******中序遍历: ");
            tree.inOrder();
            System.out.println();
    
    
            System.out.print("*******后序遍历: ");
            tree.postOrder();
            System.out.println();
    
    
            System.out.println("*******高度:" + tree.height());
            System.out.println("*******最小值:" + tree.minNode().key);
            System.out.println("*******最大值:" + tree.maxNode().key);
            System.out.println("*******树的详细信息:");
            tree.print();
            System.out.println();
    
    
            i = 8;
            System.out.printf("*******删除根节点: %d", i);
            tree.remove(i);
            System.out.println();
    
    
            System.out.println("*******高度:" + tree.height());
            System.out.println("*******中序遍历: ");
            tree.inOrder();
            System.out.println();
    
    
    
            System.out.println("*******树的详细信息:");
            tree.print();
            System.out.println();
    
            // 销毁二叉树
            tree.destroy();
        }
    }
    

      

    ---------------------------------------------------------
    学如不及,犹恐失之
  • 相关阅读:
    数据库删除Push
    MFC Tab Control控件的详细使用
    mysql char和varchar的长度
    PostgreSQL常用脚本
    Linux常用操作命令
    PostgreSQL杀掉会话
    PostgresSQL备份还原
    Grpc客户端添加请求头(Header)
    Polly重试
    名词探疑3:I18N,L10N,A11Y
  • 原文地址:https://www.cnblogs.com/noKing/p/8001608.html
Copyright © 2020-2023  润新知