二叉搜索树和平衡二叉树

写在前面

前面讲了树的基本概念,这篇文章主要讲常见的树的基本操作,如查找,新增,删除等。其中通过动图的方式使得更加容易理解。

二叉查找树

二叉查找树(BST,Binary Sort Tree),也称二叉排序树,或二叉搜索树。一棵二叉查找树满足以下条件:

  • 左子树的所有值均小于根节点的值
  • 右子树的所有值均大于根节点的值
  • 左右子树同时也满足以上两点

通俗来说就是一棵二叉树,节点 左小右大

二叉搜索树和平衡二叉树

插入操作

一棵二叉查找树的插入操作,如果插入的值 x 从根节点开始:

  1. x值小于该节点值,在左子树中继续
  2. x值大于该节点值,在右子树中继续
  3. 如果节点是叶节点,X值小于该节点值则插入左子节点,否则插入右节点

在上面的二叉排序树中,如果我们插入节点的值为 80,具体操作如下:

二叉搜索树和平衡二叉树

查找操作

一棵二叉查找树的查找操作,如果查找的值 x 从根节点开始:

  1. 如果x小于根节点,则在左子树中继续查找
  2. 如果x大于根节点,则在右子树中继续查找
  3. 如果x的值等于根节点,则返回该节点
  4. 如果都查找不到,则返回null

在上面的二叉排序树中,如果我们需要查找节点的值为10的节点,具体操作如下:

二叉搜索树和平衡二叉树

遍历树

树的遍历有三种方法。前序(preorder),中序(inorder),后序(postorder)。

前序遍历

二叉搜索树和平衡二叉树

中序遍历

二叉搜索树和平衡二叉树

后序遍历

二叉搜索树和平衡二叉树

最大值和最小值

最小值:找到根节点的左子节点,一直向左查找直到没有左子节点的节点即为最小节点

最大值:找到根节点的右子节点,一直向右查找直到没有右子节点的节点即为最小节点

删除操作

该节点是叶子节点

结点为叶子结点时,直接删除,把父节点指向子节点的引用设为null。

一棵二叉查找树的删除操作,如果删除的值 x 从根节点开始:

  1. 如果节点的值等于 x,则删除
  2. x值小于该节点值,在左子树中继续
  3. x值大于该节点值,在右子树中继续

如果我们删除节点的值为 80,具体操作如下:

二叉搜索树和平衡二叉树

该节点有一个子节点

节点有一个子节点,分两种情况,判断是父节点的左子结点还是右子节点,把父节点的引用指向结点的子节点(子节点也要分左右子节点情况,相当于一共四种情况)

左左左:即删除节点在根节点的 边,且删除节点在其父节点的 边,且删除节点的子节点为 子节点

二叉搜索树和平衡二叉树

左右左:即删除节点在根节点的 边,且删除节点在其父节点的 边,且删除节点的子节点为 子节点

二叉搜索树和平衡二叉树

右右右:即删除节点在根节点的 边,且删除节点在其父节点的 边,且删除节点的子节点为 子节点

二叉搜索树和平衡二叉树

右左右:即删除节点在根节点的 边,且删除节点在其父节点的 边,且删除节点的子节点为 子节点

二叉搜索树和平衡二叉树

该节点有两个子节点

由于二叉搜索树的特性,保证了某个结点的左子树的值都小于该节点,右子树的值都大于该节点,只需找到左子树中的最大值或者右子树中的最小值(也叫作中续后继节点)来替换该结点,即可保证节点删除后任为二叉搜索树。

后继节点

在二叉查找树中,节点是按照左小右大的方式排列的,对任意一个节点来说,比该节点的值次高的节点为它的中续后继,简称为后继节点。由于左侧节点总小于右侧节点及父节点,所以 后继节点没有左子节点,可能存在右子节点。通过后继节点来替换该结点,即可保证节点删除后仍为二叉搜索树。

查找方式也分为2种

二叉搜索树和平衡二叉树

若使用左子树中的最大值来替换

二叉搜索树和平衡二叉树

若使用右子树的最小值(后继节点)来替换

二叉搜索树和平衡二叉树

代码实现

public class BSTTree {

    /**
     * 节点
     */
    public static class Node {
        //数据,为简化代码,本程序默认节点里存储一个int变量,实际程序里可以存储自定义类型变量
        int data;
        //左子节点
        Node leftChild;
        //右子节点
        Node rightChild;

        public Node(int data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    ", leftChild=" + leftChild +
                    ", rightChild=" + rightChild +
                    '}';
        }
    }

    /**
     * 新增节点  采用递归的方式
     *
     * @param root 根节点
     * @param data 插入的数据
     * @return
     */
    public static Node insert(Node root, int data) {
        if (root == null) {
            root = new Node(data);
            return root;
        }
        //若插入的数据小于根节点,插入到其左子树
        if (data <= root.data) { root.leftchild="insert(root.leftChild," data); } else 插入到其右子树 root.rightchild="insert(root.rightChild," return root; ** * 前序遍历 @param root public static void preorder(node root) if (root !="null)" system.out.println(root.data + "->");
            preOrder(root.leftChild);
            preOrder(root.rightChild);
        }
    }

    /**
     * &#x4E2D;&#x5E8F;&#x904D;&#x5386;
     *
     * @param root
     */
    public static void inOrder(Node root) {
        if (root != null) {
            inOrder(root.leftChild);
            System.out.print(root.data + "->");
            inOrder(root.rightChild);
        }
    }

    /**
     * &#x540E;&#x5E8F;&#x904D;&#x5386;
     *
     * @param root
     */
    public static void postOrder(Node root) {
        if (root != null) {
            postOrder(root.leftChild);
            postOrder(root.rightChild);
            System.out.print(root.data + "->");
        }
    }

    /**
     * &#x67E5;&#x627E;&#x6570;&#x636E;
     *
     * @param data
     * @return
     */
    public static Node find(Node root, int data) {
        //&#x82E5;&#x67E5;&#x627E;&#x7684;&#x6570;&#x636E;&#x5C0F;&#x4E8E;&#x6839;&#x8282;&#x70B9;&#xFF0C;&#x5219;&#x5411;&#x5DE6;&#x67E5;&#x627E;&#xFF08;&#x4E5F;&#x53EF;&#x4EE5;&#x91C7;&#x7528;&#x9012;&#x5F52;&#x7684;&#x65B9;&#x5F0F;&#x67E5;&#x627E;&#xFF09;
        Node current = root;
        while (current != null) {
            if (data < current.data) {
                current = current.leftChild;
            } else if (data > current.data) {
                //&#x5411;&#x53F3;&#x67E5;&#x627E;
                current = current.rightChild;
            } else {
                return current;
            }
        }
        return null;
    }

    /**
     * &#x6700;&#x5C0F;&#x503C;
     *
     * @param root
     * @return
     */
    public static Node minimum(Node root) {
        if (root == null) {
            return null;
        }
        while (root.leftChild != null) {
            root = root.leftChild;
        }
        return root;
    }

    /**
     * &#x6700;&#x5927;&#x503C;
     *
     * @param root
     * @return
     */
    public static Node maximum(Node root) {
        if (root == null) {
            return null;
        }
        while (root.rightChild != null) {
            root = root.rightChild;
        }
        return root;
    }

    /**
     * &#x5220;&#x9664;&#x8282;&#x70B9;
     * 1.&#x8BE5;&#x8282;&#x70B9;&#x662F;&#x53F6;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x5373;&#x6CA1;&#x6709;&#x5B50;&#x8282;&#x70B9;
     * 2.&#x8BE5;&#x8282;&#x70B9;&#x6709;&#x4E00;&#x4E2A;&#x5B50;&#x8282;&#x70B9;
     * 3.&#x8BE5;&#x8282;&#x70B9;&#x6709;&#x4E24;&#x4E2A;&#x5B50;&#x8282;&#x70B9;&#xFF08;&#x901A;&#x8FC7;&#x8BE5;&#x8282;&#x70B9;&#x7684;&#x4E2D;&#x7EED;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x6765;&#x4EE3;&#x66FF;&#x9700;&#x8981;&#x5220;&#x9664;&#x7684;&#x8282;&#x70B9;&#xFF0C;
     * &#x56E0;&#x4E3A;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x6BD4;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x8282;&#x70B9;&#x90FD;&#x5C0F;&#xFF0C;&#x6BD4;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x8282;&#x70B9;&#x90FD;&#x5927;&#xFF09;
     * &#x4E2D;&#x7EED;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#xFF1A;&#x6BD4;&#x8BE5;&#x8282;&#x70B9;&#x503C;&#x6B21;&#x9AD8;&#x7684;&#x8282;&#x70B9;&#x4E3A;&#x4E2D;&#x7EED;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#xFF0C;&#x5982;&#x8282;&#x70B9;2&#x7684;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x4E3A;3
     *       4
     *      / \
     *    2    6
     *   / \  / \
     *  1  3  5  8
     *
     * @param root
     * @param data &#x8981;&#x5220;&#x9664;&#x7684;&#x8282;&#x70B9;&#x7684;&#x503C;
     */
    public static boolean delete(Node root, int data) {
        //&#x7528;&#x6765;&#x8868;&#x793A;&#x8981;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x7236;&#x8282;&#x70B9;
        Node parent = null;
        //&#x9700;&#x8981;&#x5220;&#x9664;&#x7684;&#x8282;&#x70B9;&#x662F;&#x5426;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
        boolean ifLeftChild = true;
        //&#x9700;&#x8981;&#x5220;&#x9664;&#x7684;&#x8282;&#x70B9;
        Node current = root;
        //&#x5B9A;&#x4F4D;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x4F4D;&#x7F6E;&#x53CA;&#x5176;&#x7236;&#x8282;&#x70B9;
        while (true) {
            if (data == current.data) {
                break;
            } else if (data < current.data) {
                ifLeftChild = true;
                parent = current;
                current = current.leftChild;
            } else if (data > current.data) {
                ifLeftChild = false;
                parent = current;
                current = current.rightChild;
            }
            //&#x82E5;&#x627E;&#x4E0D;&#x5230;&#x76F4;&#x63A5;&#x8FD4;&#x56DE;fasle
            if (current == null) {
                return false;
            }
        }
        //1.&#x8BE5;&#x8282;&#x70B9;&#x662F;&#x53F6;&#x5B50;&#x8282;&#x70B9;
        if (current.leftChild == null && current.rightChild == null) {
            //&#x82E5;&#x4E3A;&#x6839;&#x8282;&#x70B9;,&#x5220;&#x9664;&#x6574;&#x68F5;&#x6811;
            if (current == root) {
                root = null; //GC
            }
            //&#x82E5;&#x4E3A;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
            if (ifLeftChild) {
                parent.leftChild = null;
            } else {
                parent.rightChild = null;
            }
        }
        //2.&#x8BE5;&#x8282;&#x70B9;&#x6709;&#x4E00;&#x4E2A;&#x5B50;&#x8282;&#x70B9;
        if (current.leftChild != null && current.rightChild == null) {//&#x82E5;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x4E0D;&#x4E3A;null
            //&#x5982;&#x679C;&#x8BE5;&#x8282;&#x70B9;&#x4E3A;&#x6839;&#x8282;&#x70B9;&#xFF0C;&#x5C06;&#x6839;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x6839;&#x8282;&#x70B9;
            if (current == root) {
                root = current.leftChild;
            }
            if (ifLeftChild) {
                //&#x5DE6;&#x5DE6;&#x5DE6;&#xFF1A;&#x82E5;&#x8BE5;&#x8282;&#x70B9;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x5219;&#x8BE5;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
                parent.leftChild = current.leftChild;
            } else {
                //&#x5DE6;&#x53F3;&#x5DE6;&#xFF1A;&#x82E5;&#x8BE5;&#x8282;&#x70B9;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x5219;&#x8BE5;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;
                parent.rightChild = current.leftChild;
            }
        } else if (current.leftChild == null && current.rightChild != null) {
            if (current == root) {
                root = current.rightChild;
            }
            if (ifLeftChild) {
                //&#x53F3;&#x5DE6;&#x53F3;&#xFF1A;&#x82E5;&#x8BE5;&#x8282;&#x70B9;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x5219;&#x8BE5;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
                parent.leftChild = current.rightChild;
            } else {
                //&#x53F3;&#x53F3;&#x53F3;&#xFF1A;&#x82E5;&#x8BE5;&#x8282;&#x70B9;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x5219;&#x8BE5;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x7236;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;
                parent.rightChild = current.rightChild;
            }
        }
        //3.&#x8BE5;&#x8282;&#x70B9;&#x6709;&#x4E24;&#x4E2A;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x8FD9;&#x91CC;&#x901A;&#x8FC7;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x7684;&#x65B9;&#x5F0F;&#x5220;&#x9664;
        if (current.leftChild != null && current.rightChild != null) {
            //&#x83B7;&#x53D6;&#x5220;&#x9664;&#x8282;&#x70B9;&#x4E14;&#x91CD;&#x65B0;&#x6784;&#x5EFA;&#x7684;&#x540E;&#x7EE7;&#x7ED3;&#x70B9;
            Node successor = getSuccessor(current);
            if (root == current) {
                root = successor;
            } else if (ifLeftChild) {
                parent.leftChild = successor;
            } else {
                parent.rightChild = successor;
            }
        }
        return true;
    }

    /**
     * @param node &#x8981;&#x5220;&#x9664;&#x7684;&#x8282;&#x70B9;(&#x5047;&#x8BBE;&#x6B64;&#x65F6;&#x8BE5;&#x8282;&#x70B9;&#x5B58;&#x5728;&#x53F3;&#x5B50;&#x8282;&#x70B9;)
     * @return &#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x540E;&#x7EE7;&#x8282;&#x70B9;
     */
    public static Node getSuccessor(Node node) {
        //node&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
        Node leftChild = node.leftChild;
        //&#x5B9A;&#x4E49;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x7684;&#x7236;&#x8282;&#x70B9;
        Node successorParent = node;
        //&#x5B9A;&#x4E49;&#x540E;&#x7EE7;&#x8282;&#x70B9;
        Node successor = node;
        //&#x5B9A;&#x4E49;&#x4E00;&#x4E2A;&#x4E34;&#x65F6;&#x53D8;&#x91CF;current&#xFF0C;&#x5148;&#x83B7;&#x53D6;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#xFF0C;&#x7136;&#x540E;&#x518D;&#x83B7;&#x53D6;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x6700;&#x5C0F;&#x503C;
        Node current = node.rightChild;
        //&#x8FD9;&#x4E00;&#x6B65;&#x5C31;&#x662F;&#x67E5;&#x627E;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x540E;&#x7EE7;&#x8282;&#x70B9;
        while (current != null) {
            //&#x627E;&#x5230;&#x540E;&#x7EE7;&#x51E0;&#x70B9;&#x7684;&#x7236;&#x8282;&#x70B9;
            successorParent = successor;
            successor = current;
            //&#x83B7;&#x53D6;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x6700;&#x5C0F;&#x503C;,&#x76F4;&#x5DE6;&#x5B50;&#x6811;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x4E3A;null&#x8BF4;&#x660E;&#x8BE5;&#x8282;&#x70B9;&#x5C31;&#x662F;&#x5220;&#x9664;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x6700;&#x5C0F;&#x503C;
            current = current.leftChild;
        }
        //&#x627E;&#x5230;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x4E4B;&#x540E;&#x91CD;&#x65B0;&#x6784;&#x5EFA;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x6811;
        if (current != node.rightChild) {
            /* &#x540E;&#x7EE7;&#x8282;&#x70B9;&#x7684;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x7531;&#x539F;&#x6765;&#x7684;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x53D8;&#x4E3A;&#x539F;&#x6765;&#x540E;&#x7EE7;&#x70B9;&#x7684;&#x53F3;&#x5B50;&#x8282;&#x70B9;&#xFF08;&#x56E0;&#x4E3A;&#x5DE6;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x503C;&#x59CB;&#x7EC8;&#x5C0F;&#x4E8E;&#x7236;&#x8282;&#x70B9;&#x7684;&#x503C;&#xFF09;
             * &#x5982;&#x4E0B;55&#x82E5;&#x4E3A;&#x540E;&#x7EE7;&#x8282;&#x70B9;&#x63D0;&#x51FA;&#x53BB;&#x540E;&#xFF0C;58&#x5C31;&#x53D8;&#x4E3A;&#x4E86;60&#x7684;&#x5DE6;&#x5B50;&#x8282;&#x70B9;
             *       60                          55
             *      / \                            \
             *    55  80      ---&#x91CD;&#x65B0;&#x6784;&#x5EFA;---        60
             *     \                              / \
             *     58                            58 80
             */
            successorParent.leftChild = successor.rightChild;
            successor.rightChild = node.rightChild;
            successor.leftChild = leftChild;
        }
        return successor;
    }

    public static void main(String[] args) {
        /*
         * &#x65B0;&#x589E;&#x64CD;&#x4F5C;
         *       4
         *      / \
         *    2    6
         *   / \  / \
         *  1  3  5  8
         *          /
         *         7
         */
        Node root = null;
        root = insert(root, 4);
        root = insert(root, 2);
        root = insert(root, 1);
        root = insert(root, 3);
        root = insert(root, 6);
        root = insert(root, 5);
        root = insert(root, 8);
        root = insert(root, 7);
//        root = insert(root, 50);
//        root = insert(root, 25);
//        root = insert(root, 15);
//        root = insert(root, 35);
//        root = insert(root, 5);
//        root = insert(root, 20);
//        root = insert(root, 30);
//        root = insert(root, 40);
        //delete(root, 25);
        inOrder(root);
//        System.out.println("---------");
//        //&#x67E5;&#x627E;&#x64CD;&#x4F5C; 4
//        Node node = find(root, 4);
//        printTree(node);
//        System.out.println("---------");
//        //&#x5220;&#x9664;&#x64CD;&#x4F5C;
//        Node delete = delete(root, 4);
//        printTree(delete);
    }

    /**
     * &#x6253;&#x5370;
     *
     * @param root
     */
    public static void printTree(Node root) {
        System.out.println("&#x6839;&#x8282;&#x70B9;" + root.data);
        if (root.leftChild != null) {
            System.out.print("&#x5DE6;&#x5B50;&#x8282;&#x70B9;:");
            printTree(root.leftChild);
        }
        if (root.rightChild != null) {
            System.out.print("&#x53F3;&#x5B50;&#x8282;&#x70B9;:");
            printTree(root.rightChild);
        }
    }

}
</=>

平衡二叉树(AVL)

平衡二叉树(AVL),是一个二叉排序树,同时任意节点左右两个子树的高度差(或平衡因子,简称 BF)的绝对值不超过1,并且左右两个子树也满足。

二叉搜索树和平衡二叉树

为什么使用平衡二叉树

通过二叉查找树的查找操作可以发现,一棵二叉查找树的查找效率取决于树的高度,如果使树的高度最低,那么树的查找效率也会变高。

如下面一个二叉树,全部由右子树构成

二叉搜索树和平衡二叉树

这个时候的二叉树其实就 类似于链表,此时的查找时间复杂度为O(n),而AVL树的查找时间复杂度为O(logn)。之前讲过O(logn)耗时是小于O(n)的,如下:

二叉搜索树和平衡二叉树

可参考:常见数据结构的时间复杂度

平衡二叉树的调整

一棵平衡二叉树的插入操作会有2个结果:

如果平衡没有被打破,即任意节点的BF=1,则不需要调整
如果平衡被打破,则需要通过旋转调整,且该节点为 失衡节点

一棵失衡的二叉树通过以下调整可以重新达到平衡:

左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变

二叉搜索树和平衡二叉树

右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变

二叉搜索树和平衡二叉树

通过旋转最小失衡子树来实现失衡调整:

在一棵平衡二叉树新增节点,在新增的节点向上查找,第一个平衡因子的绝对值超过1(BF>1)的节点为根的子树称为 最小不平衡子树。也就是说,一棵失衡的树,有可能多棵子树同时失衡,这时 只需要调整最小的不平衡子树即可

看完下面的旋转方式之后再回来看最小失衡子树旋转就很清晰了

二叉搜索树和平衡二叉树

LL旋转

向左子树(L)的左孩子(L)插入新节点

插入节点失衡节点的左子树的 左边,经过一次 右旋即可达到平衡,如下

  • 插入新节点5,旧根节点40为失衡节点
  • 旧根节点40为新根节点20的右子树
  • 新根节点20的右子树30为旧根节点40的左子树

二叉搜索树和平衡二叉树

旋转过程

二叉搜索树和平衡二叉树

RR旋转

插入节点失衡节点的右子树的 右边,经过一次 左旋即可达到平衡,如下

  • 插入新节点60,旧根节点20为失衡节点
  • 旧根节点20为新根节点40的左子树
  • 新根节点40的左子树30为旧根节点20的右子树

二叉搜索树和平衡二叉树

旋转过程

二叉搜索树和平衡二叉树

LR旋转

插入节点失衡节点的左子树的 右边,先 左旋,再 右旋,如下

二叉搜索树和平衡二叉树

旋转过程

二叉搜索树和平衡二叉树

RL旋转

插入节点失衡节点的右子树的 左边,先 右旋,再 左旋,如下

二叉搜索树和平衡二叉树

旋转过程

二叉搜索树和平衡二叉树

代码实现

public class AVLTree {
    //&#x8282;&#x70B9;
    public static class Node {
        int data; //&#x6570;&#x636E;
        Node leftChild; //&#x5DE6;&#x5B50;&#x8282;&#x70B9;
        Node rightChild;//&#x53F3;&#x5B50;&#x8282;&#x70B9;
        int height; // &#x8BB0;&#x5F55;&#x8BE5;&#x8282;&#x70B9;&#x6240;&#x5728;&#x7684;&#x9AD8;&#x5EA6;

        public Node(int data) {
            this.data = data;
        }
    }
    //&#x83B7;&#x53D6;&#x8282;&#x70B9;&#x7684;&#x9AD8;&#x5EA6;
    public static int getHeight(Node p){
        return p == null ? -1 : p.height; // &#x7A7A;&#x6811;&#x7684;&#x9AD8;&#x5EA6;&#x4E3A;-1
    }
    public static void main(String[] args) {
        Node root = null;
        root = insert(root,40);
        root = insert(root,20);
        root = insert(root,50);
        root = insert(root,10);
        root = insert(root,30);
        //&#x63D2;&#x5165;&#x8282;&#x70B9;&#x5728;&#x5931;&#x8861;&#x7ED3;&#x70B9;&#x7684;&#x5DE6;&#x5B50;&#x6811;&#x7684;&#x5DE6;&#x8FB9;
        root = insert(root,5);
        //&#x6253;&#x5370;&#x6811;&#xFF0C;&#x6309;&#x7167;&#x5148;&#x6253;&#x5370;&#x5DE6;&#x5B50;&#x6811;&#xFF0C;&#x518D;&#x6253;&#x5370;&#x53F3;&#x5B50;&#x6811;&#x7684;&#x65B9;&#x5F0F;
        printTree(root);

    }

    public static void printTree(Node root) {
        System.out.println(root.data);
        if(root.leftChild !=null){
            System.out.print("left:");
            printTree(root.leftChild);
        }
        if(root.rightChild !=null){
            System.out.print("right:");
            printTree(root.rightChild);
        }
    }
    // AVL&#x6811;&#x7684;&#x63D2;&#x5165;&#x65B9;&#x6CD5;
    public static Node insert(Node root, int data) {
        if (root == null) {
            root = new Node(data);
            return root;
        }
        if (data <= root.data) { 插入到其左子树上 root.leftchild="insert(root.leftChild," data); 平衡调整 if (getheight(root.leftchild) - getheight(root.rightchild)> 1) {
                if (data <= root.leftchild.data) { 插入节点在失衡结点的左子树的左边 system.out.println("ll旋转"); root="LLRotate(root);" ll旋转调整 }else{ 插入节点在失衡结点的左子树的右边 system.out.println("lr旋转"); } 插入到其右子树上 root.rightchild="insert(root.rightChild," data); 平衡调整 if(getheight(root.rightchild) - getheight(root.leftchild)> 1){
                if(data <= 5 10 20 30 40 50 60 root.rightchild.data){ 插入节点在失衡结点的右子树的左边 system.out.println("rl旋转"); root="RLRotate(root);" }else{ system.out.println("rr旋转"); 插入节点在失衡结点的右子树的右边 } 重新调整root节点的高度值 root.height="Math.max(getHeight(root.leftChild)," getheight(root.rightchild)) + 1; return root; ** * lr旋转 public static node lrrotate(node p){ p.leftchild="RRRotate(p.leftChild);" 先将失衡点p的左子树进行rr旋转 llrotate(p); 再将失衡点p进行ll平衡旋转并返回新节点代替原失衡点p rl旋转 rlrotate(node p.rightchild="LLRotate(p.rightChild);" 先将失衡点p的右子树进行ll平衡旋转 rrrotate(p); 再将失衡点p进行rr平衡旋转并返回新节点代替原失衡点p ll旋转 右旋示意图(对结点20进行右旋) \ llrotate(node 40为失衡点 lsubtree="p.leftChild;" 失衡点的左子树的根结点20作为新的结点 将新节点的右子树30成为失衡点40的左子树 lsubtree.rightchild="p;" 将失衡点40作为新结点的右子树 重新设置失衡点40和新节点20的高度 p.height="Math.max(getHeight(p.leftChild)," getheight(p.rightchild)) lsubtree.height="Math.max(getHeight(lsubtree.leftChild)," p.height) lsubtree; 新的根节点取代原失衡点的位置 rr旋转 左旋示意图(对结点20进行左旋) rrrotate(node 20为失衡点 rsubtree="p.rightChild;" 失衡点的右子树的根结点40作为新的结点 将新节点的左子树30成为失衡点20的右子树 rsubtree.leftchild="p;" 将失衡点20作为新结点的左子树 重新设置失衡点20和新节点40的高度 rsubtree.height="Math.max(getHeight(rsubtree.leftChild)," getheight(rsubtree.rightchild)) rsubtree; < code></=></=></=>

总结

能看到这的,都是狠人。其实并不难, 主要理解左旋和右旋的概念,我觉得就很清晰了。这篇也花了我一整天时间,基本我也是从0到1去接触的,如果感兴趣可以多研究研究。

更新记录

修改时间 修改内容 2021-7-20 二叉排序树删除操作(代码逻辑错误)

Original: https://www.cnblogs.com/javatv/p/15614708.html
Author: Ayue、
Title: 二叉搜索树和平衡二叉树

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572680/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 用户态与内核态,上下文的理解

    系统调用 为了安全应用程序无法直接调用的硬件的功能,而是将这些功能封装成特定的函数。当应用程序需要硬件功能时(例如读写文件),就需要进行系统调用。当进程进行系统调用后就从用户态装换…

    Java 2023年6月6日
    069
  • NumPy学习笔记

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kuberne…

    Java 2023年6月8日
    0102
  • java.lang.ClassNotFoundException: com.*.*.entity.time.Q*

    添加依赖 1、添加DSL依赖 2、添加插件 Original: https://www.cnblogs.com/tianciliangen/p/11693647.htmlAutho…

    Java 2023年5月29日
    080
  • Java四类八种数据类型

    第一类:逻辑型boolean 第二类:文本型char 第三类:整数型(byte、short、int、long) char类型占2个字节short从-32768到32767int从-…

    Java 2023年5月29日
    094
  • Java lambda date排序

    使用lambda表达式,使用对象的时间字段将list排序。 不多说,直接上代码, Demo对象: 测试list: lambda排序: v 源码地址 Original: https:…

    Java 2023年6月8日
    0186
  • Kubernetes-StorageClass

    1. 简介 StorageClass 为管理员提供了描述存储 “类” 的方法。 通过StorageClass的定义,管理员可以将存储资源定义为某种类别(Cl…

    Java 2023年6月7日
    099
  • CSharp: Template Method Pattern

    csharp;gutter:true; /// /// Summary description for Triangle.</p> <pre><cod…

    Java 2023年6月16日
    068
  • 亚信防毒墙网络版卸载

    许多大公司(尤其是央企)都统一安装亚信防毒墙网络版,但亚信在使用中非常不友好,功能差、内存占比高,每个人都有干掉它的冲动,但卸载亚信防毒墙网络版需要本地管理员密码,多数人面对亚信防…

    Java 2023年5月30日
    0264
  • Java Math.random函数的运用

    例如取[x,y]范围内的一个随机数 int a = (int)(Math.random*(y-x+1)+x); //强转成int型,取值范围为[1,7) 若没有后面的+1,则生成[…

    Java 2023年6月15日
    086
  • 【校招VIP】[产品][一本][7分]进一步描述用户的痛点和画像

    本份简历是一位21届一本的产品同学的 简历评分: 7分 一、学员简历 二、指导意见 版式没有问题,但是项目部分的竞争力一般 1 香港这个实习本身还可以,但是描述的时候可以进一步描述…

    Java 2023年6月5日
    066
  • remoting作成windows服务后一直无法读取配置文件,可能的原因之一。

    当然在这个无法读取配置文件,无法启动通道之前,你必须确认你的配置文件是正确的。正确的动态配置remoting的格式是: service >serverProviders &g…

    Java 2023年6月14日
    091
  • js session失效退出iframe

    // 代码加入登陆页面中 if( window.top != window.self ){ window.top.location = window.location.href; …

    Java 2023年5月30日
    086
  • (转) MySQL中的意向锁

    详解 MySql InnoDB 中意向锁的作用 posted on2022-09-29 21:54 茶倌 阅读(9 ) 评论() 编辑 Original: https://www….

    Java 2023年6月8日
    081
  • 小程序开屏广告demo

    相信在座的各位都有见过大部分的应用打开的时候都会有个全屏的广告。 但是小程序的会比较少一点,因为小程序打开加载的时候已经需要消耗不少时间了,所以基本都不会去做这个,影响用户的体验。…

    Java 2023年6月16日
    0116
  • String类常用的API

    String类常用API总结及注意事项 String类常用的API 字符串内容的比较: 注意: 不能使用 == 去比较两个字符串的内容。原理:比较的是字符串的地址。(如果两个字符串…

    Java 2023年6月6日
    090
  • MySQL字符集修改

    MySQL5.7中,命令行操作sql乱码 或者在mysql可视化工具存储中文时乱码问题: 查看编码命令 show variables like ‘character_%…

    Java 2023年6月5日
    086
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球