AVL tree - self-balancing binary search tree (Java implementation)

Binary sorting tree concentrates the search advantages of arrays and the insertion and deletion advantages of linked lists, so it occupies a certain position in the data structure. However, under certain circumstances, the binary sorting tree may become a linked list, such as inserting numbers from 1 to 100. At this time, the efficiency of data search will be reduced.

In order to solve the uneven depth of the left and right subtrees of the binary sorting tree, a balanced binary tree (AVLTree) is introduced: the depth difference between the left and right subtrees of any node does not exceed 1. Through this limitation, the left and right subtrees of the binary tree are prevented. In the case of a large depth difference, the stability of the binary tree is maintained.

  How to make the depth difference between the left and right subtrees of a binary tree not exceed 1? This requires the node to be rotated, that is, when the depth of the left and right subtrees of a node exceeds 1, the node needs to be rotated (after the rotation, the left subtree is still smaller than the node and the right subtree), and the structure of the tree is readjusted.

For example, although the two binary trees have different structures, they are both binary sorted trees. The so-called rotation is to rotate the tree with a depth of 3 on the left to a binary tree with a depth of 2 on the right.

      

There are many unbalanced situations encountered in the insertion operation of a balanced binary tree, but these various situations can be decomposed into the following four basic scenarios: call it: left-left, left-right, right-right, right-left.

Before explaining these four scenarios, we need to understand a definition: minimum unbalanced node - after inserting a node, the unbalanced node closest to the inserted node is the minimum unbalanced node (as shown in the 10 nodes of the left tree in the above figure). All rotations are based on the least unbalanced node.

Continue to explain the meaning of the four case names: left left: the node is inserted in the left subtree of the left subtree of the smallest unbalanced node. Left and right: The node is inserted above the right subtree of the left subtree of the least unbalanced node

                Right: The node is inserted above the right subtree of the right subtree of the smallest unbalanced tree. Right and Left: The node is inserted above the left subtree of the right subtree of the smallest unbalanced tree.

The following four situations are analyzed in detail:

  Left and right: right-handed

Left and right are simple and do not need to be explained in detail.

Left and right: first left and then right

 

 

Some people here have doubts again. The left and left above (Figure 2) can be seen clearly, but why do the left and right scenes rotate twice? Why turn left first and then right?

Don't worry, take a look at this situation: (Picture 4)

 

毫无疑问这也是 左右 情景(左左情景有很多种,图3演示的是最基础的情景,所有 的左左情景的旋转情况和图3都是一样的),那么该怎么旋转呢?

 

直接右旋不对吧?因为6节点的右子树(以根节点10为中心,靠近内部的子树)6-8经过旋转之后要充当10节点的左子树,这样会导致依旧不平衡。所以在这种左右情景下需要进行两次旋转,先把6的右子树降低高度,然后在进行右旋。即:

把图7 情景和图3的情景一样,这就是为什么 左右情景 需要先左旋再右旋的原因。

在这里可以记作:最小不平衡节点的左节点的内部(以根节点做对称轴,偏向对称轴的为内部。也就是以7为节点的子树)的子树高度高于外部子树的高度时需要进行两次旋转。

右右:左旋

 

右右情景直接左旋即可。不在详解

右左:先右旋,再左旋

 

 

为什么这样旋转明白了吧?如同左右情景,考虑到图10的 右左情景

   

这种情景旋转如图11

旋转的四种情景就这些了。需要说明的是,下面这两对情景旋转是一样的。

图12都是右左情景,具体看代码的旋转方法就明白了在第一次右旋的时候进行的操作。private Node<T> rotateSingleRight(Node<T> node);

图13都是左右情景,第一次左旋见:private Node<T> rotateSingleLeft(Node<T> node);

旋转情景弄明白之后就是怎么代码实现了,在实现代码之前需要考虑如何进行树高判断。这里就根据定义来,|左子树树高-右子树树高|<2。如果大于等于2则该节点就不在 平衡,需要进行旋转操作。因此在程序中节点中需要定义一个height属性来存储该节点的树高。

由于平衡二叉树的性质,二叉树的高度不会很高,程序使用递归进行数据插入查找不会造成栈溢出异常,所以程序采用递归操作进行插入查找。

平衡的判定策略是在进行递归回溯的时候依照回溯路径更新节点的树高,然后根据|左子树树高-右子树树高|<2来判定该节点是否失衡,进一步对是够旋转进行判定。

程序中的平衡判定策略比较漂亮,当时就是一直卡在这里无法继续进行,然后参考了 AVL树-自平衡二叉查找树(Java实现) 之后采用这种方法才得以解决。

平衡二叉树的删除操作。

对于平衡二叉树的删除操作,只要明白一点就可以了:

    如果该节点没有左右子树(该节点为叶子节点)或者只有其中一个子树则可以直接进行删除

       否则需要继续进行判定该节点:如果该节点的外部(内外:以根节点做对称轴,靠近对称轴的子树为内部子树)子树树高低于内部子树树高,则找到该节点内部子树的最值(最值:如果内部子树是该节点的右子树则数值为右子树的最小值;如果内部节点是该节点的左子树则数值为该节点左子树的最大值)进行数值交换,交换之后删除该节点即可。

删除之后进行回溯的时候要更新节点的树高,然后判断节点是否平衡,不平衡进行旋转。这时对旋转次数的判定就不同于插入时的判定。

如图14 删除11节点

这种情景需不需要进行两次旋转?该如何判定?

毫无疑问肯定是要进行一次右旋的,但是在右旋之前是不是要进行一次左旋呢?

  这就要根据最小不平衡节点的左节点6进行判定,如果6的左节点树高低于6的右节点树高则需要进行一次左旋,最后进行一次右旋结束。

   如果6的左子树树高高于6的右子树树高则不需进行左旋可以直接对10节点进行右旋结束操作。

 如图15,这种情况肯定需要进行左旋,至于在左旋之前要不要对13节点进行右旋,相信知道该如何判断了。

  根据13节点的左右子树高度来判断,左子树(内部)高于右子树(外部)高度则需要进行左旋,图15这种情景是不需要的。

知道了各种旋转的判定标准,程序中就没有其他什么难点了,下面看一下代码:

复制代码
package com.zpj.datastructure.avlTree;

/**
 * @author PerKins Zhu
 * @date:2016年8月30日 下午8:01:03
 * @version :1.1
 * 
 */
// 存储数据类型必须实现Comparable接口,实现比较方法
public class AVLTree<T extends Comparable<T>> {
    private Node<T> root;

    // 定义节点存储数据
    private static class Node<T> {
        Node<T> left;// 左孩子
        Node<T> right;// 右孩子
        T data; // 存储数据
        int height; // 树高

        public Node(Node<T> left, Node<T> right, T data) {
            this.left = left;
            this.right = right;
            this.data = data;
            this.height = 0;
        }
    }

    // 对外公开的方法进行插入
    public Node<T> insert(T data) {
        return root = insert(data, root);
    }

    // 私有方法进行递归插入,返回插入节点
    private Node<T> insert(T data, Node<T> node) {
        // 递归终止条件
        if (node == null)
            return new Node<T>(null, null, data);
        // 比较插入数据和待插入节点的大小
        int compareResult = data.compareTo(node.data);
        if (compareResult > 0) {// 插入node的右子树
            node.right = insert(data, node.right);
            // 回调时判断是否平衡
            if (getHeight(node.right) - getHeight(node.left) == 2) {// 不平衡进行旋转
                // 判断是需要进行两次旋转还是需要进行一次旋转
                int compareResult02 = data.compareTo(node.right.data);
                if (compareResult02 > 0)// 进行一次左旋(右右)
                    node = rotateSingleLeft(node);
                else
                    // 进行两次旋转,先右旋,再左旋
                    node = rotateDoubleLeft(node);
            }
        } else if (compareResult < 0) {// 插入node的左子树
            node.left = insert(data, node.left);
            // 回调时进行判断是否平衡
            if (getHeight(node.left) - getHeight(node.right) == 2) {// 进行旋转
                // 判断是需要进行两次旋转还是需要进行一次旋转
                int intcompareResult02 = data.compareTo(node.left.data);
                if (intcompareResult02 < 0)// 进行一次左旋(左左)
                    node = rotateSingleRight(node);
                else
                    // 进行两次旋转,先左旋,再右旋
                    node = rotateDoubleRight(node);
            }
        }
        // 重新计算该节点的树高
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        return node;
    }

    // 右右情况--进行左旋
    private Node<T> rotateSingleLeft(Node<T> node) {
        Node<T> rightNode = node.right;
        node.right = rightNode.left;
        rightNode.left = node;
        // 旋转结束计算树高
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        rightNode.height = Math.max(node.height, getHeight(rightNode.right)) + 1;
        return rightNode;
    }

    // 左左情况--进行右旋
    private Node<T> rotateSingleRight(Node<T> node) {
        Node<T> leftNode = node.left;
        node.left = leftNode.right;
        leftNode.right = node;
        // 旋转结束计算树高
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        leftNode.height = Math.max(getHeight(leftNode.left), node.height) + 1;
        return leftNode;
    }

    // 右左情况--先右旋再左旋
    private Node<T> rotateDoubleLeft(Node<T> node) {
        // 先进行右旋
        node.right = rotateSingleRight(node.right);
        // 再加上左旋
        node = rotateSingleLeft(node);
        return node;
    }

    // 左右--先左旋再右旋
    private Node<T> rotateDoubleRight(Node<T> node) {
        // 先进行左旋
        node.left = rotateSingleLeft(node.left);
        // 在进行右旋
        node = rotateSingleRight(node);
        return node;
    }

    // 计算树高
    private int getHeight(Node<T> node) {
        return node == null ? -1 : node.height;
    }

    // public 方法供外部进行删除调用
    public Node<T> remove(T data) {
        return root = remove(data, root);
    }

    // 递归进行删除,返回比较节点
    private Node<T> remove(T data, Node<T> node) {
        if (node == null) {// 不存在此节店,返回null.不需要调整树高
            return null;
        }
        int compareResult = data.compareTo(node.data);
        if (compareResult == 0) {// 存在此节点进入
            /**
             * 找到节点之后进行节点删除操作 判断node是否有子树,如果没有子树或者只有一个子树则直接进行删除
             *     如果有两个子树,则需要判断node的平衡系数balance
             *         如果balance为0或者1则把node和node的左子树的最大值进行交换 否则把node和右子树的最小值进行交换
             *         交换数据之后删除该节点 删除之后判断delete节点的父节点是否平衡,如果不平衡进行节点旋转
             * 旋转之后返回delete节点的父节点进行回溯
             * */
            if (node.left != null && node.right != null) { // 此节点存在左右子树
                // 判断node节点的balance,然后进行数据交换删除节点
                int balance = getHeight(node.left) - getHeight(node.right);
                Node<T> temp = node;// 保存需要进行删除的node节点
                if (balance == -1) {
                    // 与右子树的最小值进行交换
                    exChangeRightData(node, node.right);
                } else {
                    // 与左子树的最大值进行交换
                    exChangeLeftData(node, node.left);
                }
                // 此时已经交换完成并且把节点删除完成,则需要重新计算该节点的树高
                temp.height = Math.max(getHeight(temp.left), getHeight(temp.right)) + 1;
                // 注意此处,返回的是temp,也就是保存的需要删除的节点,而不是替换的节点
                return temp;
            } else {
                // 把node的子节点返回调用处等于删除了node节点
                // 此处隐含了一个node.left ==null && node.right == null 的条件,这时返回null
                return node.left != null ? node.left : node.right;
            }
        } else if (compareResult > 0) {// 没找到需要删除的节点继续递归进行寻找
            node.right = remove(data, node.right);
            // 删除之后进行树高更新
            node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
            // 如果不平衡则进行右旋调整。
            if (getHeight(node.left) - getHeight(node.right) == 2) {// 进行旋转
                Node<T> leftSon = node.left;
                // 判断是否需要进行两次右旋还是一次右旋
                // 判断条件就是比较leftSon节点的左右子节点树高
                if (leftSon.left.height > leftSon.right.height) {
                    // 右旋一次
                    node = rotateSingleRight(node);
                } else {
                    // 两次旋转,先左旋,后右旋
                    node = rotateDoubleRight(node);
                }
            }
            return node;
        } else if (compareResult < 0) {// 没找到需要删除的节点继续递归进行寻找
            node.left = remove(data, node.left);
            // 删除之后进行树高更新
            node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
            // 如果不平衡进行左旋操作
            if (getHeight(node.left) - getHeight(node.right) == 2) {// 进行旋转
                Node<T> rightSon = node.right;
                // 判断是否需要进行两次右旋还是一次右旋
                // 判断条件就是比较rightSon节点的左右子节点树高
                if (rightSon.right.height > rightSon.left.height) {
                    node = rotateSingleLeft(node);
                } else {
                    // 先右旋再左旋
                    node = rotateDoubleLeft(node);
                }
            }
            return node;
        }
        return null;
    }

    // 递归寻找right节点的最大值
    private Node<T> exChangeLeftData(Node<T> node, Node<T> right) {
        if (right.right != null) {
            right.right = exChangeLeftData(node, right.right);
        } else {
            // 数据进行替换
            node.data = right.data;
            // 此处已经把替换节点删除
            return right.left;
        }
        right.height = Math.max(getHeight(right.left), getHeight(right.right)) + 1;
        // 回溯判断left是否平衡,如果不平衡则进行左旋操作。
        int isbanlance = getHeight(right.left) - getHeight(right.right);
        if (isbanlance == 2) {// 进行旋转
            Node<T> leftSon = node.left;
            // 判断是否需要进行两次右旋还是一次右旋
            // 判断条件就是比较leftSon节点的左右子节点树高
            if (leftSon.left.height > leftSon.right.height) {
                // 右旋一次
                return node = rotateSingleRight(node);
            } else {
                // 两次旋转,先左旋,后右旋
                return node = rotateDoubleRight(node);
            }
        }
        return right;
    }

    // 递归寻找left节点的最小值
    private Node<T> exChangeRightData(Node<T> node, Node<T> left) {
        if (left.left != null) {
            left.left = exChangeRightData(node, left.left);
        } else {
            node.data = left.data;
            // 此处已经把替换节点删除
            return left.right;
        }
        left.height = Math.max(getHeight(left.left), getHeight(left.right)) + 1;
        // 回溯判断left是否平衡,如果不平衡则进行左旋操作。
        int isbanlance = getHeight(left.left) - getHeight(left.right);
        if (isbanlance == -2) {// 进行旋转
            Node<T> rightSon = node.right;
            // 判断是否需要进行两次右旋还是一次右旋
            // 判断条件就是比较rightSon节点的左右子节点树高
            if (rightSon.right.height > rightSon.left.height) {
                return node = rotateSingleLeft(node);
            } else {
                // 先右旋再左旋
                return node = rotateDoubleLeft(node);
            }
        }
        return left;
    }

    // ************************中序输出  输出结果有小到大*************************************
    public void inorderTraverse() {
        inorderTraverseData(root);
    }

    // 递归中序遍历
    private void inorderTraverseData(Node<T> node) {
        if (node.left != null) {
            inorderTraverseData(node.left);
        }
        System.out.print(node.data + "、");
        if (node.right != null) {
            inorderTraverseData(node.right);
        }
    }

}
复制代码

 

这段测试程序可以进行测试:

复制代码
package com.zpj.datastructure.avlTree;

import org.junit.Test;

/**
 * @author PerKins Zhu
 * @date:2016年8月30日 下午8:42:15
 * @version :1.1
 * 
 */
public class AVLTreeTest {

    @Test
    public void test01() {
        AVLTree tree = new AVLTree();
        int array[] = { 28, 35, 5, 35, 26, 30, 1, 21, 18, 35, 7, 30, 25, 1, 7, };
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + ",");
            tree.insert(array[i]);
        }
        System.out.println();
        tree.inorderTraverse();
        tree.remove(12);
        System.out.println();
        tree.inorderTraverse();
    }

    @Test
    public void test02() {
        AVLTree tree = new AVLTree();
        int temp = 0;
        for (int i = 0; i < 15; i++) {
            int num = (int) (Math.random() * 40);
            System.out.print(num + ",");
            tree.insert(num);
            temp = num;
        }
        System.out.println();
        tree.inorderTraverse();
        tree.remove(temp);// 删除插入的最后一个数据
        System.out.println();
        tree.inorderTraverse();
    }

}
复制代码

 

在测试过程中对几种特殊情况都进行了测试,到目前为止没发现有问题,如果有朋友在测试的时候发现问题,欢迎指出讨论。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325979923&siteId=291194637