[06] data structure balanced binary tree (AVL trees)

@

First, the definition of balanced binary tree

Balanced binary tree , also known as AVL tree . It may be an empty tree, or with the following properties binary sort tree : the absolute value of the difference between the height of its left subtree and right subtree (equilibrium factor) and no more than one of its left subtree and the right child tree is a balanced binary tree.

From the simple definition above, we can draw some important information:

  • Balanced binary tree , also known as AVL tree
  • Must be balanced binary tree binary sort tree
  • Left subtree of each node and the right subtree of a height difference of at most 1.

Referred to the height and depth of the tree in the definition, I'm sure there are many readers must have some misconceptions about the height and depth of the tree! The most lovely misconception is that the height and depth of the tree is no different, is considered the height of the depth of the tree. Yichun would not put up with, have to beep a few ...

The essential difference between the height and depth of the tree: depth from a few root node to its leaf nodes, the height is the number of points from the leaf node to its root.

Depth starting from the root of a binary tree is a top-down layer by layer the accumulated; and a binary tree starts from the leaf node is the height from the bottom up layer by layer accumulated. Although the same depth and height of the tree, but the tree to a specific node, the depth and height is not the same.

The second is the height and depth of the tree is the number from 1 onwards, or from 0 count. Of course I have my own answer, but different opinions, bloggers do not say that right and wrong, and not to the beep. But I still agree with this point of view diagram: Here Insert Picture Description
refer to: https://www.zhihu.com/question/40286584

Second, this is not a thing or a balanced binary tree?

Analyzing a balanced binary tree (AVL tree) has the following necessary conditions:

A condition: must be a binary search tree.
The second condition: the difference in height left subtree of each node and the right subtree of at most 1.

Here Insert Picture Description
Here Insert Picture Description

Third, the balance factor

Beep much, the balance factor = left sub-tree depth / height - right sub-tree depth / height
Here Insert Picture Description
for the balanced binary tree of FIG terms:
node 5 balance factor is 3 - 2 = 1;
node 2 is a balance factor - 2 = -1;
junction balance factor 4 is 1--0 = 1;
node 6 balance factor is 0 - 1 = -1;

For the purposes of FIG unbalanced binary tree:
the node 3 is the balance factor 2--4 = -2;
node balance factor is 1 0 - 1 = -1;
junction 4 balance factor is 0--3 = -3 ;
node 5 balance factor is 0--2 = -2;
node 6 balance factor is 0 - 1 = -1;

Special attention: the balance factor are leaf nodes 0

Fourth, how to maintain a balanced binary tree balanced?

Here Insert Picture Description
Because ordinary binary search tree would be easy to lose, "balance", in extreme cases, a binary search tree would degenerate into a linear chain, result in the insertion and look down to the complexity O(n), so this is a balanced binary tree design in mind. So how to maintain balanced binary tree "balance" mean?

Not difficult to see a balanced binary tree is a height-balanced binary search tree. Therefore, to maintain a balanced binary tree to build with it are much more complex than ordinary binary tree. In the process of building a balanced binary tree, when a new node to be inserted, check whether the result is inserted and destroyed the balance of the tree, and if so, do you need to rotate to change the structure of the tree. About the rotation, I believe with descriptive words are difficult to articulate, or rely on the classic two graphs to understand better and better! Do not believe Oh, of course you can try to read the text description of L:

左旋简单来说就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点。

相信你已经晕了。当然可以试着看看下面的经典动图理解!

左旋:
Here Insert Picture Description
==试着用动态和下面的左旋结果图分析分析,想象一下,估计分分钟明白左旋!!!==
Here Insert Picture Description

 //左旋转方法代码
    private void leftRotate() {
        //创建新的结点,以当前根结点的值
        Node newNode = new Node(value);
        //把新的结点的左子树设置成当前结点的左子树
        newNode.left = left;
        //把新的结点的右子树设置成带你过去结点的右子树的左子树
        newNode.right = right.left;
        //把当前结点的值替换成右子结点的值
        value = right.value;
        //把当前结点的右子树设置成当前结点右子树的右子树
        right = right.right;
        //把当前结点的左子树(左子结点)设置成新的结点
        left = newNode;
    }

相应的右旋就很好理解了:
Here Insert Picture Description
反之就是右旋,这里就不再举例了!

小结:当二叉排序树每个节点的左子树和右子树的高度差超过1的时候,就需要通过旋转节点来维持平衡!旋转又分为左旋、右旋、双旋转。

啥?双旋转?是的,顾名思义,在一些添加节点的情况下旋转一次是不能达到平衡的,需要进行第二次旋转,

五、平衡二叉树插入节点的四种情况

当新节点插入后,有可能会有导致树不平衡,而可能出现的情况就有4种,分别称作左左,左右,右左,右右

==而所谓的“左”和“右”无非就是代表新节点所插入的位置是左还是右!==

第一个左右代表位于根节点的左或者右,
第二个左右代表位于 【最接近插入节点的拥有两个子节点的父节点】 位置的左或者右

==当然针对于第二个左右是我个人的见解,不一定完全正确。有自己想法的读者,欢迎留言指正!==

下面以左左为例,分析一波:
Here Insert Picture Description
Here Insert Picture Description
其中要特别注意的是:

右右、左左只需要旋转一次就可以平衡。
左右、右左要旋转两次才能把树调整平衡!

==其中旋转的条件就是:当二叉排序树每个节点的左子树和右子树的高度差超过1的时候!==

六、平衡二叉树操作的代码实现

// 创建AVLTree
class AVLTree {
    private Node root;

    public Node getRoot() {
        return root;
    }

    // 查找要删除的结点
    public Node search(int value) {
        if (root == null) {
            return null;
        } else {
            return root.search(value);
        }
    }

    // 查找父结点
    public Node searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }

    // 编写方法:
    // 1. 返回的 以node 为根结点的二叉排序树的最小结点的值
    // 2. 删除node 为根结点的二叉排序树的最小结点
    /**
     *
     * @param node 传入的结点(当做二叉排序树的根结点)
     *            
     * @return 返回的 以node 为根结点的二叉排序树的最小结点的值
     */
    public int delRightTreeMin(Node node) {
        Node target = node;
        // 循环的查找左子节点,就会找到最小值
        while (target.left != null) {
            target = target.left;
        }
        // 这时 target就指向了最小结点
        // 删除最小结点
        delNode(target.value);
        return target.value;
    }

    // 删除结点
    public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            // 1.需求先去找到要删除的结点 targetNode
            Node targetNode = search(value);
            // 如果没有找到要删除的结点
            if (targetNode == null) {
                return;
            }
            // 如果我们发现当前这颗二叉排序树只有一个结点
            if (root.left == null && root.right == null) {
                root = null;
                return;
            }

            // 去找到targetNode的父结点
            Node parent = searchParent(value);
            // 如果要删除的结点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                // 判断targetNode 是父结点的左子结点,还是右子结点
                if (parent.left != null && parent.left.value == value) { // 是左子结点
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) {// 是由子结点
                    parent.right = null;
                }
            } else if (targetNode.left != null && targetNode.right != null) { // 删除有两颗子树的节点
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;

            } else { // 删除只有一颗子树的结点
                // 如果要删除的结点有左子结点
                if (targetNode.left != null) {
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { // targetNode 是 parent 的右子结点
                            parent.right = targetNode.left;
                        }
                    } else {
                        root = targetNode.left;
                    }
                } else { // 如果要删除的结点有右子结点
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { // 如果 targetNode 是 parent 的右子结点
                            parent.right = targetNode.right;
                        }
                    } else {
                        root = targetNode.right;
                    }
                }

            }

        }
    }

    // 添加结点的方法
    public void add(Node node) {
        if (root == null) {
            root = node;// 如果root为空则直接让root指向node
        } else {
            root.add(node);
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (root != null) {
            root.infixOrder();
        } else {
            System.out.println("二叉排序树为空,不能遍历");
        }
    }
}

// 创建Node结点
class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {

        this.value = value;
    }

    // 返回左子树的高度
    public int leftHeight() {
        if (left == null) {
            return 0;
        }
        return left.height();
    }

    // 返回右子树的高度
    public int rightHeight() {
        if (right == null) {
            return 0;
        }
        return right.height();
    }

    // 返回 以该结点为根结点的树的高度
    public int height() {
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    //左旋转方法
    private void leftRotate() {

        //创建新的结点,以当前根结点的值
        Node newNode = new Node(value);
        //把新的结点的左子树设置成当前结点的左子树
        newNode.left = left;
        //把新的结点的右子树设置成带你过去结点的右子树的左子树
        newNode.right = right.left;
        //把当前结点的值替换成右子结点的值
        value = right.value;
        //把当前结点的右子树设置成当前结点右子树的右子树
        right = right.right;
        //把当前结点的左子树(左子结点)设置成新的结点
        left = newNode;


    }

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

    // 查找要删除的结点
    /**
     *
     * @param value
     *            希望删除的结点的值
     * @return 如果找到返回该结点,否则返回null
     */
    public Node search(int value) {
        if (value == this.value) { // 找到就是该结点
            return this;
        } else if (value < this.value) {// 如果查找的值小于当前结点,向左子树递归查找
            // 如果左子结点为空
            if (this.left == null) {
                return null;
            }
            return this.left.search(value);
        } else { // 如果查找的值不小于当前结点,向右子树递归查找
            if (this.right == null) {
                return null;
            }
            return this.right.search(value);
        }

    }

    // 查找要删除结点的父结点
    /**
     *
     * @param value 要找到的结点的值
     *            
     * @return 返回的是要删除的结点的父结点,如果没有就返回null
     */
    public Node searchParent(int value) {
        // 如果当前结点就是要删除的结点的父结点,就返回
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else {
            // 如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
            if (value < this.value && this.left != null) {
                return this.left.searchParent(value); // 向左子树递归查找
            } else if (value >= this.value && this.right != null) {
                return this.right.searchParent(value); // 向右子树递归查找
            } else {
                return null; // 没有找到父结点
            }
        }

    }

    @Override
    public String toString() {
        return "Node [value=" + value + "]";
    }

    // 添加结点的方法
    // 递归的形式添加结点,注意需要满足二叉排序树的要求
    public void add(Node node) {
        if (node == null) {
            return;
        }

        // 判断传入的结点的值,和当前子树的根结点的值关系
        if (node.value < this.value) {
            // 如果当前结点左子结点为null
            if (this.left == null) {
                this.left = node;
            } else {
                // 递归的向左子树添加
                this.left.add(node);
            }
        } else { // 添加的结点的值大于 当前结点的值
            if (this.right == null) {
                this.right = node;
            } else {
                // 递归的向右子树添加
                this.right.add(node);
            }

        }

        //当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
        if(rightHeight() - leftHeight() > 1) {
            //如果它的右子树的左子树的高度大于它的右子树的右子树的高度
            if(right != null && right.leftHeight() > right.rightHeight()) {
                //先对右子结点进行右旋转
                right.rightRotate();
                //然后在对当前结点进行左旋转
                leftRotate(); //左旋转..
            } else {
                //直接进行左旋转即可
                leftRotate();
            }
            return ; //必须要!!!
        }

        //当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
        if(leftHeight() - rightHeight() > 1) {
            //如果它的左子树的右子树高度大于它的左子树的高度
            if(left != null && left.rightHeight() > left.leftHeight()) {
                //先对当前结点的左结点(左子树)->左旋转
                left.leftRotate();
                //再对当前结点进行右旋转
                rightRotate();
            } else {
                //直接进行右旋转即可
                rightRotate();
            }
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

}

public class AVLTreeDemo {

    public static void main(String[] args) {      
        int[] arr = { 14, 21, 7, 3, 8, 9 };//任意测试节点数组
        //创建一个 AVLTree对象
        AVLTree avlTree = new AVLTree();
        //添加结点
        for(int i=0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }

        //遍历
        System.out.println("中序遍历");
        avlTree.infixOrder();

        System.out.println("平衡处理...");
        System.out.println("树的高度=" + avlTree.getRoot().height()); 
        System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); 
        System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight());
        System.out.println("当前的根结点=" + avlTree.getRoot());
    }
}

七、AVL树总结

1、平衡二叉树又称AVL树。

2, balanced binary tree query, insert, delete all the time complexity O(logN).

3, the situation is out of balance of insertion node have four, or so, left, right, left, right and right.

4, right right, or so just need to rotate once you can balance left and right, left and right to rotate twice in order to adjust the balance of the tree!

5, loss of balance up to twice as long as the rotation, so the time complexity of the process to adjust the balance O(1).

Although the effective solution of the balanced binary tree snake single list of similar extreme case, but not perfect balanced binary tree, AVL tree, the biggest drawback is that it is possible because when you delete a node imbalance, resulting in the need to start from the parent node delete nodes, continuous back to the root node, if the tree AVL tree is high, then it is necessary to determine a number of intermediate nodes, it is clear that efficiency becomes low! So we will have to learn 2-3 behind a tree and a red - black tree, taking the time to write myself ....

If this article there is a little bit of help to you, then please point a praise chant, you agree is my greatest motivation, thank you ~

Finally, if there is insufficient or is not correct, please correct me criticism, grateful! If you have questions please leave a message, the absolute first time to reply!

I welcome you to focus on the public number, there are some java learning materials and a large wave of java e-books, such as Zhou Zhiming teacher depth java virtual machine, java programming ideas, the core technology volume, Westward design patterns, java concurrent programming combat ... .. is a java Bible, do not say fast car on Tomcat, ye who go! The main thing is to explore technology, yearning technology, the pursuit of technology, said good pots Friends is coming Oh ...

Here Insert Picture Description

Guess you like

Origin www.cnblogs.com/yichunguo/p/12040456.html