数据结构-平衡二叉树(ALV树)

一、平衡二叉树的定义

首先,平衡二叉树是一棵二叉查找树。此外,他的每一个结点的左子树和右子树的高度之差都小于等于1l
因为平衡二叉树的平衡特性(每一个结点的左子树和右子树的高度之差都小于等于1),它的搜索效率很高log(n)。一颗n个结点的平衡二叉树的高度最大是log(n) + 1。

平衡因子:我们将平衡二叉树上结点的左子树深度减去右子树深度的值称为平衡因子。平衡二叉树上所有结点的平衡因子都只可能是0, 1, -1。

最小不平衡子树:当往平衡二叉树中插入一个结点,造成这棵二叉树不再平衡时,我们称以距离插入结点最近的并且平衡因子绝对值大于1的结点为根的子树叫做最小不平衡子树。
请找出下图中的最小不平衡子树。


图一 - 找出最小不平衡子树

二、平衡二叉树的实现原理

2.1 基本旋转操作

平衡二叉树是在二叉查找树原理的基础上,每次增加或者删除结点后,都应该重新维持树的平衡。
维持树的平衡的基本操作就是旋转。
首先我们看一下最基础的右旋(左旋和右旋道理一样)。


图二 - 简单右旋

我们假设cur为需要右旋的结点,parent = cur.parent,left = cur.left。
那么右旋的伪代码就是

    public void rightRotate(BiTNode cur) {
        BiTNode parent = cur.parent;
        BiTNode left = cur.left;
        left.parent = parent;  //连接左子结点和父节点
        if (parent.left == cur) { 
            parent.left = left;
        } else {
            parent.right = left;
        }
        cur.left = left.right;  //使left的右子结点称为cur的左子结点
        if (left.right != null) {
            left.right.parent = cur;
        }
        left.right = cur;  //使cur称为left的右子结点
        cur.parent = left;
    }

2.2 两次旋转操作

有些情况下,不能用一次旋转就完成树的平衡。比如如下这种情况。


图三 - 结点和左子结点的平衡因子的正负值不一样

上图中结点中黑色数字表示结点存储的值,红色数字表示结点的平衡因子。结点5的平衡因子是2,因此结点5需要右旋。但是结点5的左子结点结点2的平衡因子是-1,直接右旋依然是不平衡的,如下图所示。


图四 - 结点和左子结点的平衡因子的正负值不一样的情况下一次旋转不能平衡

这时候,我们应该先将结点2左旋,使得他和结点5的平衡因子正负值一样,然后再对结点5右旋。
图五 - 两次旋转达到平衡

2.3 插入一个结点

首先我们这样定义平衡二叉树的结点。他相比二叉查找树结点多了一个平衡因子,用于存储当前结点的平衡状态;还多了一个父节点,因为java不能传指针,因此需要一个父节点引用来进行删除操作。

    //平衡二叉树结点
    public static class BiTNode {
        int val = 0; //结点的值
        int bf = 0; //平衡因子
        BiTNode left = null;  //左子树
        BiTNode right = null;  //右子树
        BiTNode parent = null;  //父结点
    }

平衡二叉树的插入可以分为两个步骤:1.插入一个结点,2.进行平衡操作。

2.3.1 插入结点

首先我们按照二叉查找树的操作插入一个结点(递归)。
伪代码如下

            if (cur == null) {  //插入结点
                cur = new BiTNode();
                cur.val = key;
                cur.bf = 0;
                cur.parent = parent;
                taller = true;
                if (parent != null) {
                    if (parent.val < cur.val) {
                        parent.right = cur;
                    } else {
                        parent.left = cur;
                    }
                } else {
                        root = cur;
                        cur.parent = null;
                }
             else if (cur.val == key) {  //已有结点,不需要插入
                taller = false;
                return false;
            } else if (cur.val > key) { //插入到左子树中
                insert(cur.left, cur, key)
            } else {  //插入右子树中
                insert(cur.right, cur, key)
            }
2.3.2 平衡操作

进行回溯,不断计算插入路径中父节点的平衡因,出现不平衡状况,进行旋转使之重新平衡。

三、代码

package AVLTree;

public class AVLTree {
    
    private BiTNode root = null;
    private boolean taller = false;
    
    public void setRoot(int data) {
        if (root == null) {
            root = new BiTNode();
            root.val = data;
        } else {
            root.val = data;
        }
    }
    /**
     * 插入前是平衡状态,插入后可能会是不平衡状态,旋转后是平衡状态
     * @param key
     * @return
     */
    public boolean insert(int key) {
        return insert(root, null, key);
    }
    
    private boolean insert(BiTNode cur, BiTNode parent, int key) {
        if (cur == null) {  //插入结点
            cur = new BiTNode();
            cur.val = key;
            cur.bf = 0;
            cur.parent = parent;
            taller = true;
            if (parent != null) {
                if (parent.val < cur.val) {
                    parent.right = cur;
                } else {
                    parent.left = cur;
                }
            } else {
                root = cur;
                cur.parent = null;
            }
        } else {   //搜索,插入并平衡
            if (cur.val == key) {  //已有结点,不需要插入
                taller = false;
                return false;
            } else if (cur.val > key) { //插入到左子树中
                if (!insert(cur.left, cur, key)) {  //插入,如果已存在,不需要平衡
                    return false;
                }
                if (taller) {    //如果有增长,需要平衡
                    if (cur.bf == 0) {
                        taller = true;
                        cur.bf = 1;
                    } else if (cur.bf == 1) {
                        leftBalance(cur);
                        taller = false;
                    } else {
                        cur.bf = 0;
                        taller = false;
                    }
                }
            } else {  //插入右子树中
                if (!insert(cur.right, cur, key)) {  //插入,如果已存在,不需要平衡
                    return false;
                }
                if (taller) {    //如果有增长,需要平衡
                    if (cur.bf == 0) {
                        taller = true;
                        cur.bf = -1;
                    } else if (cur.bf == 1) {
                        cur.bf = 0;
                        taller = false;
                    } else {
                        rightBalance(cur);
                        taller = false;
                    }
                }
            }
        }
        return true;
    }
    
    /**
     *       5                       5
     *      *  *                    *  *
     *     2     6                 3    6
     *    *  *                    *  *
     *   1    3                  2    4
     *         *                *
     *          4              1
     * @param node
     */
    public void leftBalance(BiTNode node) {
        BiTNode leftSon = node.left;
        BiTNode leftSonRight = leftSon.right;
        switch (leftSon.bf) {
            case 1:
                node.bf = 0;
                rightBalance(node);
                break;
            case -1:
                switch(leftSonRight.bf) {
                    case 1:
                        node.bf = -1;
                        leftSon.bf = 0;
                        break;
                    case -1:
                        node.bf = 0;
                        leftSon.bf = 1;
                        break;
                    case 0:
                        node.bf = 0;
                        leftSon.bf = 0;
                        break;
                }
                leftSonRight.bf = 0;
                leftRotate(leftSon);
                rightRotate(node);
                break;
        }
    }
    /**
     *        4                            4                      4                     4
     *       *  *                         *  *                   *  *                  *  *
     *     2     6                       2    6                 2    7                2    7
     *         *   *                         *  *                   *  *                  *  *
     *        5     7                       5    8                 6    8                5    8
     *                *                         *                 *                       *
     *                  8                      7                 5                         6
     * @param node
     */
    public void rightBalance(BiTNode node) {
        BiTNode rightSon = node.left;
        BiTNode rightSonLeft = null;
        switch (rightSon.bf) {
            case -1:
                rightSon.bf = 0;
                leftRotate(node);
                break;
            case 1:
                switch (rightSonLeft.bf) {
                    case 0:  //不可能存在这种情况
                        node.bf = 0;
                        rightSon.bf = 0;
                        break;
                    case 1:
                        node.bf = 0;
                        rightSon.bf = -1;
                        break;
                    case -1:
                        node.bf = 1;
                        rightSon.bf = 0;
                        break;
                }
                rightSonLeft.bf = 0;
                rightRotate(rightSon);
                leftRotate(node);
                break;
        }
    }
    /**
     * 简单右旋
     * @param node
     */
    public void rightRotate(BiTNode node) {
        BiTNode parent = node.parent;
        BiTNode leftSon = node.left;
        leftSon.parent = parent;
        if (parent != null) {
            if (parent.left == node) {
                parent.left = leftSon;
            } else {
                parent.right = leftSon;
            }
        } else {
            root = leftSon;
            leftSon.parent = null;
        }
        node.left = leftSon.right;
        if (leftSon.right != null) {
            leftSon.right.parent = node;
        }
        leftSon.right = node;
        node.parent = leftSon;
    }
    /**
     * 简单左旋
     * @param node
     */
    public void leftRotate(BiTNode node) {
        BiTNode parent = node.parent;
        BiTNode rightSon = node.right;
        rightSon.parent = parent;
        if (parent != null) {
            if (parent.left == node) {
                parent.left = rightSon;
            } else {
                parent.right = rightSon;
            }
        } else {
            root = rightSon;
            rightSon.parent = null;
        }
        node.right = rightSon.left;
        if (rightSon.left != null) {
            rightSon.left.parent = node;
        }
        rightSon.left = node;
        node.parent = rightSon;
    }
    
    //平衡二叉树结点
    public static class BiTNode {
        int val = 0; //结点的值
        int bf = 0; //平衡因子
        BiTNode left = null;  //左子树
        BiTNode right = null;  //右子树
        BiTNode parent = null;  //父结点
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容