二叉平衡樹(AVL樹)

平衡二叉樹(Balanced Binary Tree)又被稱為AVL樹(有別于AVL算法)鸠窗,且具有以下性質(zhì):
它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹榔幸。
這個方案很好的解決了二叉查找樹退化成鏈表的問題,把插入冻河,查找钝计,刪除的時間復(fù)雜度最好情況和最壞情況都維持在O(logN)。但是頻繁旋轉(zhuǎn)會使插入和刪除犧牲掉O(logN)左右的時間瘫絮,不過相對二叉查找樹來說涨冀,查找時間上穩(wěn)定了很多。

二叉樹不平衡的情景麦萤,及其解決方案

這里為了避免一直看概念比較”干“鹿鳖,會結(jié)合代碼進行說明

節(jié)點的定義

public class AvlTree<T extends Comparable<T>> {

    /**
     * 樹的根節(jié)點
     */
    private AvlNode root;

    /**
     * 樹的高度
     */
    private int height;

    private class AvlNode {
        /**
         * 節(jié)點值
         */
        private T data;
        /**
         * 當(dāng)前節(jié)點所在樹的高度
         */
        int height;
        /**
         * 左右子樹
         */
        private AvlNode left, right;

        AvlNode(T data) {
            left = right = null;
            this.data = data;
            this.height = 0;
        }
    }
}

1. 左左

左旋

節(jié)點6的左子樹高度為3扁眯,右子樹高度為1,而且6節(jié)點的任意子樹的左子樹的高度都大于等于右子樹的高度栓辜,這種形態(tài)的二叉樹直接進行左旋即可恋拍。

編碼實現(xiàn)
    /**
     * 左旋
     *       4                2
     *      / \              / \
     *     2   5    --->    1  4
     *    / \              /  / \
     *   1   3            0   3  5
     *  /
     * 0
     *
     * @param avlNode 當(dāng)前根節(jié)點
     * @return 左旋后的當(dāng)前根節(jié)點
     */
    private AvlNode ll(AvlNode avlNode) {
        if (avlNode==null) {
            return null;
        }
        // 獲取當(dāng)前傳入二叉樹根節(jié)點的左兒子
        AvlNode leftNode = avlNode.left;
        // 左旋
        avlNode.left = leftNode.right;
        leftNode.right = avlNode;
        // 重新計算高度
        leftNode.height = Integer.max(this.height(leftNode.left), this.height(leftNode.right))+1;
        avlNode.height = Integer.max(this.height(avlNode), this.height(avlNode))+1;
        // 此時當(dāng)前二叉樹的根節(jié)點為剛剛的左兒子
        return leftNode;
    }

2. 右右

右旋

節(jié)點2的右子樹的高度為3,左子樹的高度為1藕甩,而且2節(jié)點的任意右子樹的高度都大于等于左子樹施敢,這種形態(tài)的二叉樹直接進行右旋即可。

編碼實現(xiàn)
    /**
     * 右旋
     *     2                     4
     *    / \                   / \
     *   1   4                 2  5
     *      / \     --->      / \  \
     *     3   5              1  3  6
     *          \
     *           6
     *
     * @param avlNode 當(dāng)前根節(jié)點
     * @return 右旋后的當(dāng)前根節(jié)點
     */
    private AvlNode rr(AvlNode avlNode) {
        if (avlNode==null) {
            return null;
        }
        // 獲取當(dāng)前傳入二叉樹根節(jié)點的右兒子
        AvlNode rightNode = avlNode.right;
        // 右旋
        avlNode.right = rightNode.left;
        rightNode.left = avlNode;
        // 重新計算高度
        rightNode.height = Integer.max(this.height(rightNode.left), this.height(rightNode.right))+1;
        avlNode.height = Integer.max(this.height(avlNode.left), this.height(avlNode.right))+1;
        // 此時當(dāng)前二叉樹的根節(jié)點為剛剛的右兒子
        return rightNode;
    }

2. 左右

左右

節(jié)點6的左子樹的高度為2狭莱,右子樹的高度為1僵娃,但是不具備任意左子樹的高度都大于等于右子樹,即腋妙,節(jié)點2的左子樹的高度為1默怨,但是右子樹的高度為2,這種情況首先需要將節(jié)點2進行右旋骤素,轉(zhuǎn)換成”左左“形式匙睹,然后在對節(jié)點6進行左旋,最終達到平衡济竹。

編碼實現(xiàn)
    /**
     * 左右模式痕檬,先右旋,然后左旋
     *        6                 6                 4
     *       / \               / \              /  \
     *      2   7             4   7            2    6
     *     / \      --->     / \     --->     / \  / \
     *    1   4             2   5            1   3 5   7
     *       / \           / \
     *      3   5         1   3
     * @param avlNode 當(dāng)前根節(jié)點
     * @return 旋轉(zhuǎn)后的當(dāng)前根節(jié)點
     */
    private AvlNode lr(AvlNode avlNode){
        if(avlNode==null){
            return null;
        }
        avlNode.left = rr(avlNode.left);
        return rr(avlNode);
    }

3. 右左

右左

節(jié)點2的左子樹的高度為1送浊,右子樹的高度為3梦谜,但是不具備任意右子樹的高度都大于等于左子樹的高度情況,即袭景,節(jié)點6的左子樹高度為2唁桩,右子樹的高度為1,這種情況首先要將節(jié)點6進行左旋耸棒,轉(zhuǎn)換成”右右“形式荒澡,然后對節(jié)點2進行右旋,最終達到平衡与殃。

編碼實現(xiàn)
    /**
     * 右左模式仰猖,先左旋,然后右旋
     *           2                2                   4
     *          / \              / \                /   \
     *         1   6            1  4               2     6
     *            / \   --->      / \       --->  / \   / \
     *           4   7           3   6           1   3 5  7
     *          / \                 / \
     *         3   5               5   7
     *
     * @param avlNode 當(dāng)前根節(jié)點
     * @return 旋轉(zhuǎn)后的根節(jié)點
     */
    private AvlNode rl(AvlNode avlNode){
        if(avlNode==null){
            return null;
        }
        avlNode.right = ll(avlNode.right);
        return rr(avlNode);
    }

整體創(chuàng)建二叉樹的過程就是這樣的

  1. 插入新的節(jié)點
  2. 調(diào)整數(shù)的平衡
  3. 重新計算節(jié)點高度
    /**
     * 插入節(jié)點
     *
     * @param data 節(jié)點值
     */
    public void insert(T data) {
        root = insertCore(data, root);
        height = root.height;
    }

    /**
     * 插入節(jié)點核心邏輯
     *
     * @param data 數(shù)據(jù)
     * @param root 當(dāng)前節(jié)點根節(jié)點
     * @return 根節(jié)點
     */
    private AvlNode insertCore(T data, AvlNode root) {
        if (root == null) {
            return new AvlNode(data);
        }
        if (root.data.compareTo(data) > 0) {
            root.left = insertCore(data, root.left);
            // 是否需要平衡
            if ((height(root.left) - height(root.right) > 1)) {
                if (height(root.left.left)>height(root.left.right)) {
                    // LL情況
                    root = ll(root);
                } else {
                    // LR情況
                    root = lr(root);
                }
            }
        } else if (root.data.compareTo(data) < 0) {
            root.right = insertCore(data, root.right);
            // 是否需要平衡
            if ((height(root.right) - height(root.left) > 1)) {
                if (height(root.right.right)>height(root.right.left)) {
                    // RR情況
                    root = rr(root);
                } else {
                    // RL情況
                    root = rl(root);
                }
            }
        }
        // 重新計算節(jié)點高度
        if(root != null){
            root.height = Integer.max(height(root.left), height(root.right)) + 1;
        }
        // 節(jié)點和當(dāng)前root節(jié)點相等奈籽,直接返回
        return root;
    }

刪除節(jié)點

刪除節(jié)點的方式有很多種饥侵,這里只是其中的一種

同插入操作一樣,刪除結(jié)點時也有可能破壞平衡性衣屏,這就要求我們刪除的時候要進行平衡性調(diào)整躏升。

刪除分為以下幾種情況:
  1. 首先在整個二叉樹中搜索要刪除的結(jié)點,如果沒搜索到直接返回不作處理狼忱,否則執(zhí)行以下操作:
  2. 要刪除的節(jié)點是當(dāng)前根節(jié)點T膨疏。
    1. 如果左右子樹都非空一睁。在高度較大的子樹中實施刪除操作。
      分兩種情況:
      1. 左子樹高度大于右子樹高度佃却,將左子樹中最大的那個元素賦給當(dāng)前根節(jié)點者吁,然后刪除左子樹中元素值最大的那個節(jié)點。
      2. 左子樹高度小于右子樹高度饲帅,將右子樹中最小的那個元素賦給當(dāng)前根節(jié)點复凳,然后刪除右子樹中元素值最小的那個節(jié)點。
    2. 如果左右子樹中有一個為空灶泵,那么直接用那個非空子樹或者是NULL替換當(dāng)前根節(jié)點即可育八。
  3. 要刪除的節(jié)點元素值小于當(dāng)前根節(jié)點T值,在左子樹中進行刪除赦邻。遞歸調(diào)用髓棋,在左子樹中實施刪除。否則在右子樹中遞歸調(diào)用實施刪除惶洲。
  4. 刪除節(jié)點后按声,需要判斷當(dāng)前根節(jié)點是否仍然滿足平衡條件,如果滿足平衡條件恬吕,只需要更新當(dāng)前根節(jié)點T的高度信息签则。否則,需要進行旋轉(zhuǎn)調(diào)整币呵。
編碼實現(xiàn)
    /**
     * 找到樹中最小的值
     *
     * @param root 樹的根節(jié)點
     * @return min avlNode
     */
    private AvlNode findMin(AvlNode root) {
        return (root == null || root.left == null) ? root : findMin(root.left);
    }

    /**
     * 找到樹中最大的值
     *
     * @param root 樹的根節(jié)點
     * @return max avlNode
     */
    private AvlNode finMax(AvlNode root) {
        return (root == null || root.right == null) ? root : finMax(root.right);
    }
    /**
     * 刪除節(jié)點
     *
     * @param data 節(jié)點值
     */
    public void delete(T data) {
        root = deleteCore(data, root);
        height = root!=null?root.height:0;
    }

    /**
     * 刪除節(jié)點核心邏輯
     *
     * @param data 數(shù)據(jù)
     * @param root 當(dāng)前節(jié)點根節(jié)點
     * @return 根節(jié)點
     */
    private AvlNode deleteCore(T data, AvlNode root) {
        if (root == null) {
            return null;
        }
        if (root.data.compareTo(data) > 0) {
            // 根節(jié)點比刪除的節(jié)點大,刪除的節(jié)點在左子樹
            root.left = deleteCore(data, root.left);
            // 平衡樹
            if (height(root.right) - height(root.left) > 1) {
                // 判斷平衡模式
                if (height(root.right.left) > height(root.right.right)) {
                    // RL模式
                    root = rl(root);
                } else {
                    // RR模式
                    root = rr(root);
                }
            }
        } else if (root.data.compareTo(data) < 0) {
            // 根節(jié)點比刪除的節(jié)點小侨颈,刪除的單節(jié)點在右子樹
            root.right = deleteCore(data, root.right);
            // 平衡樹
            if (height(root.left) - height(root.right) > 1) {
                // 判斷平衡模式
                if (height(root.left.right) > height(root.left.left)) {
                    // LR模式
                    root = lr(root);
                } else {
                    // LL模式
                    root = ll(root);
                }
            }
        } else {
            // 當(dāng)前的根節(jié)點即為需要刪除的節(jié)點

            if (root.left != null && root.right != null) {
                // 左右子樹都非空余赢,判斷左右子樹的高度,選擇高度較高的一個

                if (root.left.height > root.right.height) {
                    // 左子樹較高哈垢,那么從左子樹中找到值最大的節(jié)點妻柒,跟要刪除的節(jié)點的值(當(dāng)前的根節(jié)點)進行替換,然后刪除該節(jié)點
                    AvlNode maxNode = finMax(root.left);
                    root.data = maxNode.data;
                    // 刪除左子樹中值最大的節(jié)點
                    root.left = deleteCore(maxNode.data, root.left);
                } else {
                    // 右子樹較高耘分,那么從右子樹中找到值最小的節(jié)點举塔,跟要刪除的節(jié)點的值(當(dāng)前的根節(jié)點)進行替換,然后刪除該節(jié)點
                    AvlNode minNode = findMin(root.right);
                    root.data = minNode.data;
                    // 刪除右子樹中值最小的節(jié)點
                    root.right = deleteCore(minNode.data, root.right);
                }

            } else {
                // 選擇任意一個非空節(jié)點作為根節(jié)點返回
                root = root.left != null ? root.left : root.right;
            }

        }
        // 重新計算節(jié)點的高度
        if(root!=null){
            root.height = Integer.max(height(root.left), height(root.right)) + 1;
        }
        return root;
    }

查找

查找跟普通的二叉樹查找相同求泰,就不墨跡概念了央渣,直接上代碼

    /**
     * 判斷節(jié)點是否在二叉樹中
     *
     * @param data data
     * @return bool
     */
    public boolean contains(T data){
        AvlNode p = root;
        while (p!=null&&p.data!=data){
            if(p.data.compareTo(data)>0){
                p=p.left;
            }else {
                p=p.right;
            }
        }
        return p!=null&&p.data.compareTo(data)==0;
    }

完整代碼:https://github.com/xiao-ren-wu/leet-code-practise/blob/master/src/main/java/org/ywb/practise/tree/AvlTree.java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渴频,隨后出現(xiàn)的幾起案子芽丹,更是在濱河造成了極大的恐慌,老刑警劉巖卜朗,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拔第,死亡現(xiàn)場離奇詭異咕村,居然都是意外死亡,警方通過查閱死者的電腦和手機蚊俺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門懈涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泳猬,你說我怎么就攤上這事批钠。” “怎么了暂殖?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵价匠,是天一觀的道長。 經(jīng)常有香客問我呛每,道長踩窖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任晨横,我火速辦了婚禮洋腮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘手形。我一直安慰自己啥供,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布库糠。 她就那樣靜靜地躺著伙狐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞬欧。 梳的紋絲不亂的頭發(fā)上贷屎,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音艘虎,去河邊找鬼唉侄。 笑死,一個胖子當(dāng)著我的面吹牛野建,可吹牛的內(nèi)容都是我干的属划。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼候生,長吁一口氣:“原來是場噩夢啊……” “哼同眯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唯鸭,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嗽测,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唠粥,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡疏魏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晤愧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片大莫。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖官份,靈堂內(nèi)的尸體忽然破棺而出只厘,到底是詐尸還是另有隱情,我是刑警寧澤舅巷,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布羔味,位于F島的核電站,受9級特大地震影響钠右,放射性物質(zhì)發(fā)生泄漏赋元。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一飒房、第九天 我趴在偏房一處隱蔽的房頂上張望搁凸。 院中可真熱鬧,春花似錦狠毯、人聲如沸护糖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫡良。三九已至,卻和暖如春献酗,著一層夾襖步出監(jiān)牢的瞬間寝受,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工凌摄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羡蛾,地道東北人漓帅。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓锨亏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忙干。 傳聞我的和親對象是個殘疾皇子器予,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容