数据结构—平衡二叉树

  二叉排序树集中了数组的查找优点以及链表的插入、删除优点,所以在数据结构中占有必定的地位。但在必定的状况下二叉排序树又有可能变为链表,例如插入从1~100的数,这时进行数据查找的效率就要下降。node

为了解决二叉排序树这种左右子树深度不均匀的状况引入了一种平衡二叉树(AVLTree):任何一个节点的左右子树深度差不超过1.经过这个限定,阻止了二叉树的左右子树深度差较大的状况,维持了二叉树的稳定。数组

  如何让二叉树的左右子树深度差不超过1呢?这就须要对节点进行旋转,也就是当某个节点的左右子树深度超过1时须要对这个节点进行旋转(旋转以后依旧是左子树小于节点小于右子树),从新调整树的结构。数据结构

例如:这两棵二叉树虽然结构不一样,可是都是二叉排序树,所谓的旋转就是把左边的深度为3的树旋转为右边深度为2的二叉树。dom

      

在平衡二叉树进行插入操做时遇到的不平衡状况有多种,可是这么多种状况均可以分解为一下四中基础情景:把它叫作:左左、左右、右右、右左。测试

在解释这四种情景以前须要先明白一个定义:最小不平衡节点—插入一个节点以后,距离这个插入节点最近的不平衡节点就是最小不平衡节点(如上图左树的10节点)。全部的旋转都是在最小不平衡节点的基础上进行的。this

继续解释四种情景命名意义:左左:节点插入在最小不平衡节点的左子树的左子树上。     左右:节点插入在最小不平衡节点的左子树的右子树上面spa

                右:节点插入在最小不平衡树的右子树的右子树上面。   右左:节点插入在最小不平衡树的右子树的左子树上面。.net

下面就具体分析这四种状况:3d

  左左:右旋code

左左简单不用详解。

左右:先左旋再右旋

这里有人又有疑问了,上面的左左(图2)看明白了,可这里左右情景为何要旋转两次呢?为何先左旋,再右旋呢?

先别急,看看这种状况:(图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();
    }

}

 

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

------------------------------------------------------    

相关文章
相关标签/搜索