數(shù)據(jù)結(jié)構(gòu)-平衡二叉樹(ALV樹)

一器钟、平衡二叉樹的定義

首先崇裁,平衡二叉樹是一棵二叉查找樹哀澈。此外滓窍,他的每一個(gè)結(jié)點(diǎn)的左子樹和右子樹的高度之差都小于等于1l
因?yàn)槠胶舛鏄涞钠胶馓匦裕恳粋€(gè)結(jié)點(diǎn)的左子樹和右子樹的高度之差都小于等于1)与帆,它的搜索效率很高log(n)了赌。一顆n個(gè)結(jié)點(diǎn)的平衡二叉樹的高度最大是log(n) + 1。

平衡因子:我們將平衡二叉樹上結(jié)點(diǎn)的左子樹深度減去右子樹深度的值稱為平衡因子玄糟。平衡二叉樹上所有結(jié)點(diǎn)的平衡因子都只可能是0勿她, 1, -1阵翎。

最小不平衡子樹:當(dāng)往平衡二叉樹中插入一個(gè)結(jié)點(diǎn)逢并,造成這棵二叉樹不再平衡時(shí),我們稱以距離插入結(jié)點(diǎn)最近的并且平衡因子絕對(duì)值大于1的結(jié)點(diǎn)為根的子樹叫做最小不平衡子樹郭卫。
請(qǐng)找出下圖中的最小不平衡子樹砍聊。


圖一 - 找出最小不平衡子樹

二、平衡二叉樹的實(shí)現(xiàn)原理

2.1 基本旋轉(zhuǎn)操作

平衡二叉樹是在二叉查找樹原理的基礎(chǔ)上贰军,每次增加或者刪除結(jié)點(diǎn)后玻蝌,都應(yīng)該重新維持樹的平衡。
維持樹的平衡的基本操作就是旋轉(zhuǎn)词疼。
首先我們看一下最基礎(chǔ)的右旋(左旋和右旋道理一樣)俯树。


圖二 - 簡(jiǎn)單右旋

我們假設(shè)cur為需要右旋的結(jié)點(diǎn),parent = cur.parent寒跳,left = cur.left聘萨。
那么右旋的偽代碼就是

    public void rightRotate(BiTNode cur) {
        BiTNode parent = cur.parent;
        BiTNode left = cur.left;
        left.parent = parent;  //連接左子結(jié)點(diǎn)和父節(jié)點(diǎn)
        if (parent.left == cur) { 
            parent.left = left;
        } else {
            parent.right = left;
        }
        cur.left = left.right;  //使left的右子結(jié)點(diǎn)稱為cur的左子結(jié)點(diǎn)
        if (left.right != null) {
            left.right.parent = cur;
        }
        left.right = cur;  //使cur稱為left的右子結(jié)點(diǎn)
        cur.parent = left;
    }

2.2 兩次旋轉(zhuǎn)操作

有些情況下竹椒,不能用一次旋轉(zhuǎn)就完成樹的平衡童太。比如如下這種情況。


圖三 - 結(jié)點(diǎn)和左子結(jié)點(diǎn)的平衡因子的正負(fù)值不一樣

上圖中結(jié)點(diǎn)中黑色數(shù)字表示結(jié)點(diǎn)存儲(chǔ)的值胸完,紅色數(shù)字表示結(jié)點(diǎn)的平衡因子书释。結(jié)點(diǎn)5的平衡因子是2,因此結(jié)點(diǎn)5需要右旋赊窥。但是結(jié)點(diǎn)5的左子結(jié)點(diǎn)結(jié)點(diǎn)2的平衡因子是-1爆惧,直接右旋依然是不平衡的,如下圖所示锨能。


圖四 - 結(jié)點(diǎn)和左子結(jié)點(diǎn)的平衡因子的正負(fù)值不一樣的情況下一次旋轉(zhuǎn)不能平衡

這時(shí)候扯再,我們應(yīng)該先將結(jié)點(diǎn)2左旋芍耘,使得他和結(jié)點(diǎn)5的平衡因子正負(fù)值一樣,然后再對(duì)結(jié)點(diǎn)5右旋熄阻。
圖五 - 兩次旋轉(zhuǎn)達(dá)到平衡

2.3 插入一個(gè)結(jié)點(diǎn)

首先我們這樣定義平衡二叉樹的結(jié)點(diǎn)斋竞。他相比二叉查找樹結(jié)點(diǎn)多了一個(gè)平衡因子,用于存儲(chǔ)當(dāng)前結(jié)點(diǎn)的平衡狀態(tài)秃殉;還多了一個(gè)父節(jié)點(diǎn)坝初,因?yàn)閖ava不能傳指針,因此需要一個(gè)父節(jié)點(diǎn)引用來進(jìn)行刪除操作钾军。

    //平衡二叉樹結(jié)點(diǎn)
    public static class BiTNode {
        int val = 0; //結(jié)點(diǎn)的值
        int bf = 0; //平衡因子
        BiTNode left = null;  //左子樹
        BiTNode right = null;  //右子樹
        BiTNode parent = null;  //父結(jié)點(diǎn)
    }

平衡二叉樹的插入可以分為兩個(gè)步驟:1.插入一個(gè)結(jié)點(diǎn)鳄袍,2.進(jìn)行平衡操作。

2.3.1 插入結(jié)點(diǎn)

首先我們按照二叉查找樹的操作插入一個(gè)結(jié)點(diǎn)(遞歸)吏恭。
偽代碼如下

            if (cur == null) {  //插入結(jié)點(diǎn)
                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) {  //已有結(jié)點(diǎn)拗小,不需要插入
                taller = false;
                return false;
            } else if (cur.val > key) { //插入到左子樹中
                insert(cur.left, cur, key)
            } else {  //插入右子樹中
                insert(cur.right, cur, key)
            }
2.3.2 平衡操作

進(jìn)行回溯,不斷計(jì)算插入路徑中父節(jié)點(diǎn)的平衡因樱哼,出現(xiàn)不平衡狀況十籍,進(jìn)行旋轉(zhuǎn)使之重新平衡。

三唇礁、代碼

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;
        }
    }
    /**
     * 插入前是平衡狀態(tài)勾栗,插入后可能會(huì)是不平衡狀態(tài),旋轉(zhuǎn)后是平衡狀態(tài)
     * @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) {  //插入結(jié)點(diǎn)
            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) {  //已有結(jié)點(diǎn)围俘,不需要插入
                taller = false;
                return false;
            } else if (cur.val > key) { //插入到左子樹中
                if (!insert(cur.left, cur, key)) {  //插入,如果已存在琢融,不需要平衡
                    return false;
                }
                if (taller) {    //如果有增長(zhǎng)界牡,需要平衡
                    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) {    //如果有增長(zhǎng)宿亡,需要平衡
                    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;
        }
    }
    /**
     * 簡(jiǎn)單右旋
     * @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;
    }
    /**
     * 簡(jiǎn)單左旋
     * @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;
    }
    
    //平衡二叉樹結(jié)點(diǎn)
    public static class BiTNode {
        int val = 0; //結(jié)點(diǎn)的值
        int bf = 0; //平衡因子
        BiTNode left = null;  //左子樹
        BiTNode right = null;  //右子樹
        BiTNode parent = null;  //父結(jié)點(diǎn)
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纳令,隨后出現(xiàn)的幾起案子挽荠,更是在濱河造成了極大的恐慌,老刑警劉巖平绩,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圈匆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捏雌,警方通過查閱死者的電腦和手機(jī)跃赚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來性湿,“玉大人纬傲,你說我怎么就攤上這事满败。” “怎么了叹括?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵葫录,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我领猾,道長(zhǎng)米同,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任摔竿,我火速辦了婚禮面粮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘继低。我一直安慰自己熬苍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布袁翁。 她就那樣靜靜地躺著柴底,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粱胜。 梳的紋絲不亂的頭發(fā)上柄驻,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音焙压,去河邊找鬼鸿脓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涯曲,可吹牛的內(nèi)容都是我干的野哭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幻件,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拨黔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绰沥,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤篱蝇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后揪利,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體态兴,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年疟位,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喘垂。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甜刻,死狀恐怖绍撞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情得院,我是刑警寧澤傻铣,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站祥绞,受9級(jí)特大地震影響非洲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜕径,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一两踏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兜喻,春花似錦梦染、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至遂铡,卻和暖如春肮疗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扒接。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工族吻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人珠增。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓超歌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蒂教。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巍举,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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