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

定義

平衡二叉樹(shù)勃痴,是對(duì)二叉搜索樹(shù)的一種優(yōu)化季春。

向二叉搜索樹(shù)中插入元素時(shí)洗搂,不同的插入次序,將構(gòu)造出不同結(jié)構(gòu)的樹(shù)载弄。通俗來(lái)講耘拇,就是會(huì)導(dǎo)致樹(shù)的深度平均查找長(zhǎng)度(ASL averge search length)不同;以下圖為例宇攻。

bst_tree_insert

明顯可以看出惫叛,中間(b)這種結(jié)構(gòu)是比較好的。整個(gè)二叉搜索樹(shù)左右兩邊顯得比較平均逞刷,不像最后一種完全成了一顆右斜樹(shù)嘉涌,或者說(shuō)是單向鏈表,同時(shí)也可以看到其ASL=3.0 是這三種結(jié)構(gòu)中最小的夸浅。

總的來(lái)說(shuō)洛心,目的就是想讓整個(gè)二叉搜索樹(shù)變得比較矮胖,而不是高瘦题篷,或者是一邊倒的傾斜。因?yàn)樘浚忠馕吨鴺?shù)比較低番枚,使得查找某個(gè)元素的能更快速。

平衡因子:Balance Factor,簡(jiǎn)稱BF,BF(T)=hL-hR. 其中L和hR分別為二叉樹(shù)左右子樹(shù)的高度损敷。

平衡二叉樹(shù):(AVL 樹(shù)):空樹(shù)葫笼,或者任一結(jié)點(diǎn)左、右子樹(shù)高度差的絕對(duì)值不超過(guò)1. 即|BF(T)|<=1.

avl_tree

圖中結(jié)點(diǎn)3和5的平衡因子均為2拗馒,因此不是平衡二叉樹(shù)路星。最后一棵樹(shù),根節(jié)點(diǎn)7平衡因子為2诱桂,同樣不是平衡二叉樹(shù)洋丐。

對(duì)于給定結(jié)點(diǎn)數(shù)為n的AVL樹(shù),最大高度為O(log2n).

也就說(shuō)挥等,從n個(gè)數(shù)中友绝,查找一個(gè)特定值時(shí),最多需要log2n次肝劲。因此迁客,AVL 是一種特別適合進(jìn)行查找操作的樹(shù)郭宝。

平衡二叉樹(shù)的調(diào)整

四種失衡的情況

在平衡二叉樹(shù)中,當(dāng)我們插入新的元素時(shí)掷漱,為了保證二叉搜索樹(shù)的特性粘室,很容易導(dǎo)致某些結(jié)點(diǎn)失衡,即該結(jié)點(diǎn)的平衡因子大于1卜范。

而在二叉樹(shù)中衔统,任意結(jié)點(diǎn)孩子最多只有左右兩個(gè),而且導(dǎo)致失去平衡的必要條件就是當(dāng)前結(jié)點(diǎn)的兩顆子樹(shù)的高度差等于2先朦。因此缰冤,致使一個(gè)結(jié)點(diǎn)失衡的插入操作有以下4中。

  • 在結(jié)點(diǎn)的左子樹(shù)的左子樹(shù)插入元素,LL 插入喳魏;
  • 在結(jié)點(diǎn)的左子樹(shù)的右子樹(shù)插入元素,LR 插入棉浸;
  • 在結(jié)點(diǎn)的右子樹(shù)的左子樹(shù)插入元素,RL 插入;
  • 在結(jié)點(diǎn)的右子樹(shù)的右子樹(shù)插入元素,RR 插入刺彩。
失去平衡的二叉搜索樹(shù)
  • LL(一)中結(jié)點(diǎn)1的插入導(dǎo)致結(jié)點(diǎn)8失衡迷郑,而插入的位置是在其左子樹(shù)的左子樹(shù)上,同樣LL(二)中创倔,結(jié)點(diǎn)3插入同樣導(dǎo)致結(jié)點(diǎn)8失衡嗡害,這里需要注意子樹(shù)是從受影響的結(jié)點(diǎn)算起,雖然3插在了右邊畦攘,但他依舊是在8(失衡結(jié)點(diǎn))左子樹(shù)的左子樹(shù)上霸妹,因此屬于LL 插入。
  • LR(一),結(jié)點(diǎn)5插入導(dǎo)致結(jié)點(diǎn)8失衡知押,插入位置是在其左子樹(shù)的右子樹(shù)上叹螟,同樣LR(二)結(jié)點(diǎn)7的插入也是同理,因此這二者都屬于LR 插入台盯。

后面兩種失衡現(xiàn)象可以當(dāng)做是前兩者的鏡像罢绽,原理都是一樣的。

對(duì)四種失衡情況的調(diào)整策略

面對(duì)以上4種失衡的情況静盅,在AVL 樹(shù)中將采用LL(左左)良价,LR(左右),RR(右右)和RL(右左) 四種旋轉(zhuǎn)方式進(jìn)行調(diào)整蒿叠。

1. LL(左左)旋轉(zhuǎn)

ll

如圖明垢,這種情況下BL結(jié)點(diǎn)插入元素導(dǎo)致結(jié)點(diǎn)A失衡,因此我們的操作就是圍繞失衡的結(jié)點(diǎn)(圖中A)和導(dǎo)致其失衡的結(jié)點(diǎn)(圖中B)進(jìn)行栈虚。具體來(lái)說(shuō)就是圍繞結(jié)點(diǎn)B 將樹(shù)順時(shí)針(左手)旋轉(zhuǎn)袖外,最終結(jié)果就是B成為了根節(jié)點(diǎn)(相對(duì)而言),A 變成了B的右子樹(shù)魂务,而B(niǎo)原來(lái)的右子樹(shù)(BR)變成了A 的左子樹(shù)曼验。

看一下代碼實(shí)現(xiàn):

    /**
     * 左旋
     *
     * @param node 失衡結(jié)點(diǎn) 
     * @return 旋轉(zhuǎn)后根節(jié)點(diǎn)
     */
    private TreeNode<T> leftRotate(TreeNode<T> node) {
        // 將失衡結(jié)點(diǎn)的左子樹(shù)賦給一個(gè)臨時(shí)結(jié)點(diǎn)泌射,也就是將A的左子樹(shù)B 賦給新的結(jié)點(diǎn)
        TreeNode<T> newRoot = node.leftChild;
        // 將B 被右子樹(shù)BR 掛在A 的左子樹(shù)上
        node.leftChild = newRoot.rightChild;
        // B 的右子樹(shù)為失衡的結(jié)點(diǎn)即A
        newRoot.rightChild = node;
        // 結(jié)點(diǎn)A 的高度為左右子樹(shù)高度最大值加1
        node.height = getMax(height(node.leftChild), height(node.rightChild)) + 1;
        // 結(jié)點(diǎn)B 的高度為左右子樹(shù)高度最大值加1
        newRoot.height = getMax(height(newRoot.leftChild), newRoot.height) + 1;
        // 返回根節(jié)點(diǎn)
        return newRoot;
    }

結(jié)合注釋?xiě)?yīng)該很好理解。

2. RR(右右)旋轉(zhuǎn)

rr

理解了LL鬓照,RR就是同理了熔酷,圍繞的同樣是導(dǎo)致失衡的結(jié)點(diǎn)B,只不過(guò)旋轉(zhuǎn)方向變成了逆時(shí)針(右手向內(nèi))。

    /**
     * 右旋
     *
     * @param node
     * @return
     */
    private TreeNode<T> rightRotate(TreeNode<T> node) {
        TreeNode<T> newRoot = node.rightChild;
        node.rightChild = newRoot.leftChild;
        newRoot.leftChild = node;

        node.height = getMax(height(node.leftChild), height(node.rightChild)) + 1;
        newRoot.height = getMax(height(newRoot.rightChild), node.height) + 1;

        return newRoot;
    }

3. LR(右左)旋轉(zhuǎn)

lr

看上面的圖可能有點(diǎn)暈豺裆,這里看以具體的例子拒秘。

rl_example

上圖中Jan結(jié)點(diǎn)的插入導(dǎo)致May結(jié)點(diǎn)失衡,而Jan結(jié)點(diǎn)又處在May結(jié)點(diǎn)左子樹(shù)的右子樹(shù)上臭猜,是LR 插入導(dǎo)致的失衡躺酒,面對(duì)這種情況我們可以進(jìn)行LR旋轉(zhuǎn),需要關(guān)注的三個(gè)結(jié)點(diǎn)是May,Aug和Mar蔑歌。具體來(lái)說(shuō)羹应,LR 旋轉(zhuǎn)可以分解為RR旋轉(zhuǎn)和LL旋轉(zhuǎn)。首先圍繞Aug和Mar,進(jìn)行一次RR旋轉(zhuǎn)次屠,然后圍繞Mar和May在進(jìn)行一次LL旋轉(zhuǎn)园匹。這樣最終就完成了LR 旋轉(zhuǎn),最終的結(jié)果是樹(shù)仍然為AVL樹(shù)劫灶。

    /**
     * LR 左右旋轉(zhuǎn)
     *
     * @param node
     * @return
     */
    private TreeNode<T> leftRightRotate(TreeNode<T> node) {
        // 首先圍繞失衡結(jié)點(diǎn)的左子樹(shù)(圖中Aug) 和Mar進(jìn)行一次右旋裸违,這樣Mar 和 Aug 換了位置
        node.leftChild = rightRotate(node.leftChild);
        // 最后,圍繞May和Mar進(jìn)行一次左旋
        return leftRotate(node);
    }

4. RL(左右)旋轉(zhuǎn)

rl

前面說(shuō)過(guò)了本昏,RL其實(shí)就是LR 的鏡像供汛,因此這里道理都是一樣的,只過(guò)順序顛倒而已涌穆。

/**
     * RL 右左旋轉(zhuǎn)
     *
     * @param node
     * @return
     */
    private TreeNode<T> rightLeftRotate(TreeNode<T> node) {
        node.rightChild = leftRotate(node.rightChild);
        return rightRotate(node);
    }

AVL 樹(shù)的實(shí)現(xiàn)

以上就是AVL 樹(shù)所有的理論基礎(chǔ)紊馏,下面看看如何去實(shí)現(xiàn)。

  • 結(jié)點(diǎn)的定義

AVL 樹(shù)首先是二叉搜索樹(shù)蒲犬,因此它的結(jié)點(diǎn)也必須是可比較。同時(shí)為了方便岸啡,會(huì)加入一個(gè)表示當(dāng)前結(jié)點(diǎn)高度的height字段原叮。

/**
 * Created by engineer on 2017/10/31.
 *
 * AVL 樹(shù)節(jié)點(diǎn)定義
 */

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

    // 數(shù)據(jù)域
    private T data;
    // 左子樹(shù)
    public TreeNode<T> leftChild;
    // 右子樹(shù)
    public TreeNode<T> rightChild;

    //當(dāng)前結(jié)點(diǎn)的高度
    public int height;


    public TreeNode(T data) {
        this(null, data, null);
    }

    public TreeNode(TreeNode leftChild, T data, TreeNode rightChild) {
        this(data, leftChild, rightChild, 0);
    }

    public TreeNode(T data, TreeNode<T> leftChild, TreeNode<T> rightChild, int height) {
        this.data = data;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
        this.height = height;
    }

    public T getData() {
        return data;
    }

    public TreeNode<T> getLeftChild() {
        return leftChild;
    }

    public TreeNode<T> getRightChild() {
        return rightChild;
    }

    public void setData(T data) {
        this.data = data;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}
  • 平衡二叉樹(shù)插入
/**
     * 插入結(jié)點(diǎn)
     *
     * @param value
     */
    public void insert(T value) {
        root = insert(root, value);
    }

    private TreeNode<T> insert(TreeNode<T> node, T value) {
        if (node == null) {
            // 新建節(jié)點(diǎn)
            node = new TreeNode<T>(value);
            if (node == null) {
                return null;
            }
        } else {
            int cmp = value.compareTo(node.getData());

            if (cmp < 0) {    // 應(yīng)該將value插入到"node的左子樹(shù)"的情況
                node.leftChild = insert(node.leftChild, value);
                // 插入節(jié)點(diǎn)后,若AVL樹(shù)失去平衡巡蘸,則進(jìn)行相應(yīng)的調(diào)節(jié)奋隶。
                if (height(node.leftChild) - height(node.rightChild) == 2) {
                    if (value.compareTo(node.leftChild.getData()) < 0)
                        node = leftRotate(node);
                    else
                        node = leftRightRotate(node);
                }
            } else if (cmp > 0) {    // 應(yīng)該將value插入到"node的右子樹(shù)"的情況
                node.rightChild = insert(node.rightChild, value);
                // 插入節(jié)點(diǎn)后,若AVL樹(shù)失去平衡悦荒,則進(jìn)行相應(yīng)的調(diào)節(jié)唯欣。
                if (height(node.rightChild) - height(node.leftChild) == 2) {
                    if (value.compareTo(node.rightChild.getData()) > 0)
                        node = rightRotate(node);
                    else
                        node = rightLeftRotate(node);
                }
            } else {    // cmp==0
                System.out.println("添加失敗:不允許添加相同的節(jié)點(diǎn)搬味!");
            }
        }

        node.height = getMax(height(node.leftChild), height(node.rightChild)) + 1;

        return node;
    }

測(cè)試平衡二叉樹(shù)

public class AvlTreeTest {

    private static Integer[] arrays = new Integer[]{10, 8, 3, 12, 9, 4, 5, 7, 1, 11, 17};

    public static void main(String[] args) {
        AvlTree<Integer> mAvlTree = new AvlTree<>();
        for (int i = 0; i < arrays.length; i++) {
            mAvlTree.insert(arrays[i]);
        }

        mAvlTree.printTree();
    }
}

這里我們測(cè)試平衡二叉樹(shù)采用和上一節(jié)二叉搜索樹(shù)中同樣的數(shù)據(jù)境氢。首先看一下樹(shù)遍歷打印結(jié)果:

前序遍歷:8 4 3 1 5 7 10 9 12 11 17 
中序遍歷:1 3 4 5 7 8 9 10 11 12 17 
后序遍歷:1 3 7 5 4 9 11 17 12 10 8 

這樣的遍歷的結(jié)構(gòu)蟀拷,相對(duì)應(yīng)的平衡二叉樹(shù)將是如下:

平衡二叉樹(shù)

在和上一節(jié)構(gòu)造的二叉樹(shù)對(duì)比一下:

二叉搜索樹(shù)

很明顯,平衡二叉樹(shù)是一種更加友好的搜索樹(shù)萍聊,在平衡二叉樹(shù)中查找7這個(gè)元素问芬,最大比較4次,而在普通的二叉搜索樹(shù)中需要找6次寿桨〈诵疲總體來(lái)說(shuō),平衡二叉樹(shù)結(jié)合平衡因子構(gòu)造出了一顆十分便于查找的二叉搜索樹(shù)亭螟。


好了挡鞍,平衡二叉樹(shù)就到這里了。


參考文檔

AVL樹(shù)(三)之 Java的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末预烙,一起剝皮案震驚了整個(gè)濱河市墨微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌默伍,老刑警劉巖欢嘿,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異也糊,居然都是意外死亡炼蹦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)狸剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掐隐,“玉大人,你說(shuō)我怎么就攤上這事钞馁÷鞘。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵僧凰,是天一觀的道長(zhǎng)探颈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)训措,這世上最難降的妖魔是什么伪节? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮绩鸣,結(jié)果婚禮上怀大,老公的妹妹穿的比我還像新娘。我一直安慰自己呀闻,他們只是感情好化借,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著捡多,像睡著了一般蓖康。 火紅的嫁衣襯著肌膚如雪铐炫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天钓瞭,我揣著相機(jī)與錄音驳遵,去河邊找鬼。 笑死山涡,一個(gè)胖子當(dāng)著我的面吹牛堤结,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸭丛,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼竞穷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鳞溉?” 一聲冷哼從身側(cè)響起瘾带,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熟菲,沒(méi)想到半個(gè)月后看政,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抄罕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年允蚣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呆贿。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嚷兔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出做入,到底是詐尸還是另有隱情冒晰,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布竟块,位于F島的核電站壶运,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浪秘。R本人自食惡果不足惜前弯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秫逝。 院中可真熱鬧,春花似錦询枚、人聲如沸违帆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刷后。三九已至的畴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尝胆,已是汗流浹背丧裁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留含衔,地道東北人煎娇。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像贪染,于是被迫代替她去往敵國(guó)和親缓呛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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