平衡二叉樹 AVL樹結構詳解 [Java實現]

作者

NeroJings

來源

https://blog.csdn.net/zhang6622056/article/details/82698859

本文思維導圖

簡述

先不說平衡二叉樹,我們單開來說,這樣比較方便理解。

先說二叉樹,再說平衡條件,沒那麼多花裏胡哨的理論,我只是想讓大家看完能明白,能寫出來。

二叉樹

什麼是二叉樹?二叉樹數據結構,顧名思義,只有兩個叉,在數據結構中,操作性能要遠高於線性結構,有O(height)的索引性能。與線性結構有相同的空間複雜度,特性如下:

  • 每個節點最多隻有兩個兒子節點
  • 左兒子小,右兒子大 (大小按照我們默認的比較規則,本例用int來比較)

線行找7與二叉數找7

線性找7

二叉樹找7

okay,我想大家聰明人已經看出來了,二叉樹搜索用了2次,而線性結構卻用了5次。

說白了,二叉樹結構,我每次問一個節點,都會離着我的目標越來越近,但是線性的則不然,我必須一個個問。

說到這兒,我想會有博友提出質疑了,如果線性查找,7恰好就在第一個呢?那不是一下就找到了嗎?

哈哈,你怎麼不上天呢 - -。還第一個。開個小玩笑。

這就是二叉樹索引的好處。相比看圖比碼字要清楚的多。

平衡條件

那麼,什麼叫平衡呢?其實很簡單,任何一個節點的子節點高度差必須小於2

第一個二叉平衡樹

  • 從下往上數,第一個高度爲1(比較符合日常生活數數),那我們數數吧
  • 5:———1高度 | 4,7,23,71 ———2高度| 6,50 ———3高度 | 15 ———4高度
  • 比如節點6,那麼4和7的高度都是2,那就2-2 < 2 。平衡!!

難點一 遞歸

遞歸查找

我又加入了一些節點,方便大家理解遞歸深度

  • 每一次正向橙色線條的滾動,就是一次遞歸查找
  • 每一次正向橙色線條的滾動,方法的入棧!
  • 遞歸的深度,取決於線條走了幾次,那就有多大的棧深度
  • 本次查找,刨除root,共4次進棧

難點二 回溯

插入回溯

先不要關心這個旋轉操作,如圖所示,我們在遞歸的基礎上,沿着線條理解一下回溯

  • 每一次逆向橙色線條的滾動,就是一次回溯
  • 操作遞歸的每一個節點,都會在回溯的軌跡上
  • 正因爲每一次遞歸,都有每一次回溯,那麼,我們就可以先完成相關操作(增加或刪除)之後,判定平衡

4種旋轉

左左類型旋轉

博主儘量放慢了速度,讓大家看清楚究竟旋轉是如何進行的,這是一個插入操作,我們看到在不平衡的時候,進行了左旋轉,這裏我們看到

  • 正向插入,遞歸3-2-1
  • 逆向回溯,1-2 判斷平衡條件 ,是平衡的
  • 再次回溯,2-3,3的左邊高度爲2,右邊沒有節點爲0,那麼2-0 > 1,不平衡!

到這裏我們基本上理解了平衡的判斷,下面正式說一下旋轉:

  • 判斷不平衡邊 在3節點判定,不平衡,那麼左邊高,我們需要調整左邊,獲取左邊節點2
  • 判斷旋轉類型 這時候我們拿到節點2,判斷節點2哪邊高。左邊高,爲左左類型。右邊高爲左右旋轉類型,我們先不管
  • 旋轉操作 3.left = 2.right; 2.right = 3; 重新計算,2和3節點的高度

右右類型旋轉[同上,不再敘述]

左右類型旋轉

右左類型旋轉

到此旋轉就說完了,希望大家好好的理解第一個左左類型!(理解了一個也就都理解了 )

後續部分沒有講是因爲說太多反而更亂。

後續的理解不了沒關係,我們代碼在看。

代碼基礎部分

node類

public static class AvlNodeInteger{
        private Integer value;
        private Integer height;
        private AvlNodeInteger left;
        private AvlNodeInteger right;

        public AvlNodeInteger(int t){
            initNode(t,null,null,1);
        }

        public AvlNodeInteger(int t,AvlNodeInteger left,AvlNodeInteger right){
            initNode(t,left,right,null);
        }

        private void initNode(int t,AvlNodeInteger left,AvlNodeInteger right,Integer height){
            this.setValue(t);
            this.left = left;
            this.right = right;
            this.height = height;
        }

        public Integer getValue() {
            return value;
        }

        public void setValue(Integer value) {
            this.value = value;
        }

        public Integer getHeight() {
            return height;
        }

        public void setHeight(Integer height) {
            this.height = height;
        }

        public AvlNodeInteger getLeft() {
            return left;
        }

        public void setLeft(AvlNodeInteger left) {
            this.left = left;
        }

        public AvlNodeInteger getRight() {
            return right;
        }

        public void setRight(AvlNodeInteger right) {
            this.right = right;
        }
    }

高度計算

/***
     * 求一個節點的高度
     * @param t
     * @return
     */
    private int height(AvlNodeInteger t){
        return null == t ? 0 : t.getHeight();
    }
/***
     * 求左右子節點最大高度
     * @param left
     * @param right
     * @return
     */
    private int maxHeight(AvlNodeInteger left,AvlNodeInteger right){
        return height(left) > height(right) ? height(left)  : height(right);
    }

插入操作

旋轉

/***
     * 左左旋轉模型
     * @param node  旋轉之前的parent node 節點
     * @return  旋轉之後的parent node節點
     */
    private AvlNodeInteger leftLeftRotate(AvlNodeInteger node){
        AvlNodeInteger newRoot = node.getLeft();
        node.setLeft(newRoot.getRight());
        newRoot.setRight(node);

        //由此node的高度降低了,newRoot的高度提高了。
         //newRoot的高度由node的高度而來
        node.setHeight(maxHeight(node.getLeft(),node.getRight())+1);
        newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight())+1);
        return newRoot;
    }


    /***
     * 右右旋轉模型
     * @param node
     * @return
     */
    private AvlNodeInteger rightRightRotate(AvlNodeInteger node){
        AvlNodeInteger newRoot = node.getRight();
        node.setRight(newRoot.getLeft());
        newRoot.setLeft(node);

        //由此node的高度降低了,newRoot的高度提高了。
        //newRoot的高度由node的高度而來
        node.setHeight(maxHeight(node.getLeft(),node.getRight()));
        newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight()));
        return newRoot;
    }

    /**
     * 左右模型,先右右,再左左
     * @param node
     * @return
     */
    private AvlNodeInteger leftRightRotate(AvlNodeInteger node){
        //注意傳遞的參數
        node.setLeft(rightRightRotate(node.getLeft()));
        return leftLeftRotate(node);
    }

    /***
     * 右左模型,先左左,在右右
     * @param node
     * @return
     */
    private AvlNodeInteger rightLeftRotate(AvlNodeInteger node){
        node.setRight(leftLeftRotate(node.getRight()));
        return rightRightRotate(node);
    }

insert

/****
     * 對外開放,插入操作
     * @param val
     * @throws Exception
     */
    public void insert(Integer val) throws Exception {
        if(null == root){
            initRoot(val);
            size++;
            return;
        }

        if(contains(val)) throw new Exception("The value is already exist!");

        insertNode(this.root,val);
        size++;
    }


    /**
     * 遞歸插入
     * parent == null 到最底部插入前節點判斷情況
     * @param parent
     * @param val
     * @return
     */
    private AvlNodeInteger insertNode(AvlNodeInteger parent,Integer val){
        if(parent == null){
           return createSingleNode(val);
        }
        if(val < parent.getValue()){    //插入判斷,小於父節點,插入到右邊
            //注意理解回溯,這裏最終返回的是插入完成節點
            //每一層回溯,都會返回相應當時遞歸的節點!!!
            parent.setLeft(insertNode(parent.getLeft(),val));

            //判斷平衡,不要在意這裏的parent是誰,
            //這個parent肯定是遞歸層級上,回溯的一個節點!每一個節點都需要判斷平衡
            if(height(parent.getLeft()) - height(parent.getRight()) > 1){
                Integer compareVal = (Integer) parent.getLeft().getValue();
                //左左旋轉類型
                if(val < Integer.valueOf(compareVal)){
                    parent = leftLeftRotate(parent);
                }else{                  //左右旋轉類型
                    parent = leftRightRotate(parent);
                }
            }
        }
        if(val > parent.getValue()){   //插入判斷,小於父節點,插入到右邊
            //注意理解回溯,這裏最終返回的是插入完成節點
            //每一層回溯,都會返回相應當時遞歸的節點!!!
            parent.setRight(insertNode(parent.getRight(),val));

            //判斷平衡,不要在意這裏的parent是誰,
            //這個parent肯定是遞歸層級上,回溯的一個節點!每一個節點都需要判斷平衡
            if(height(parent.getRight()) - height(parent.getLeft()) > 2){
                Integer compareVal = (Integer) parent.getLeft().getValue();
                if(val > compareVal){
                    parent = rightRightRotate(parent);
                }else{
                    parent = rightLeftRotate(parent);
                }
            }
        }

        parent.setHeight((maxHeight(parent.getLeft(),parent.getRight()))+1);
        return parent;
    }

刪除操作

public void remove(Integer val) {
        if(null == val || null == root){
            return;
        }
        if(!contains(val)){
            return;
        }
        remove(root,val);
    }


    /****
     * AVL刪除,平衡樹實現
     * @param parent
     * @param val
     * @return
     */
    private AvlNodeInteger remove(AvlNodeInteger parent,Integer val){
        if(val < parent.getValue()){        //左子樹遞歸查詢
            //刪除以後返回替換的新節點
            AvlNodeInteger newLeft = remove(parent.getLeft(),val);
            parent.setLeft(newLeft);
            //檢查是否平衡,刪除的左邊,那麼用右邊-左邊
            if(height(parent.getRight()) - height(parent.getLeft()) > 1){
                AvlNodeInteger tempNode = parent.getRight();
                if(height(tempNode.getLeft()) > height(tempNode.getRight())){       //RL類型
                    rightLeftRotate(parent);
                }else{      //RR類型
                    rightRightRotate(parent);
                }
            }
        }else if(val > parent.getValue()){  //右子樹遞歸查找
            //刪除以後返回替換的新節點
            AvlNodeInteger newRight = remove(parent.getRight(),val);
            parent.setRight(newRight);
            //檢查是否平衡
            if(height(parent.getLeft()) - height(parent.getRight()) > 1){
                    AvlNodeInteger tempNode = parent.getLeft();
                    if(height(tempNode.getLeft()) > height(tempNode.getRight())){   //LL類型
                        leftLeftRotate(parent);
                    }else{             //LR類型
                        leftRightRotate(parent);
                    }
            }
        }else{   //相等,匹配成功
            if(null != parent.getLeft() && null != parent.getRight()){  //左右子節點都不爲空
                //判斷高度,高的一方,拿到最大(左),最小(右)的節點,作爲替換節點。
                //刪除原來匹配節點
                //左邊更高,獲取到左邊最大的節點
                if(parent.getLeft().getHeight() > parent.getRight().getHeight()){
                    AvlNodeInteger leftMax = getMax(parent.getLeft());
                    parent.setLeft(remove(parent.getLeft(),leftMax.getValue()));
                    leftMax.setLeft(parent.getLeft());
                    leftMax.setRight(parent.getRight());
                    leftMax.setHeight(maxHeight(leftMax.getLeft(),leftMax.getRight()));
                    parent = leftMax;
                }else{                  //右邊更高,獲取到右邊最小的節點
                    AvlNodeInteger rightMin = getMin(parent.getRight());
                    parent.setRight(remove(parent.getRight(),rightMin.getValue()));
                    rightMin.setLeft(parent.getLeft());
                    rightMin.setRight(parent.getRight());
                    rightMin.setHeight(maxHeight(parent.getLeft(),parent.getRight())+1);
                    parent = rightMin;
                }
            }else{
                //有任意一方節點爲空,則不爲空的那一方作爲替換節點,刪除原來的節點
                parent = null;
            }
        }

        return parent;
    }


    /***
     * 刪除時用到,獲取當前節點子節點最大值
     * @param currentRoot
     * @return
     */
    private AvlNodeInteger getMax(AvlNodeInteger currentRoot){
            if(currentRoot.getRight() != null){
                currentRoot = getMax(currentRoot.getRight());
            }
            return currentRoot;
    }

    /***
     * 刪除時用到,獲取當前節點子節點最小值
     * @param currentRoot
     * @return
     */
    private AvlNodeInteger getMin(AvlNodeInteger currentRoot){
            if(currentRoot.getLeft() != null){
                currentRoot = getMin(currentRoot.getLeft());
            }
            return currentRoot;
    }

以上就是難點插入和刪除的實現了, 沒有過多闡述,是因爲大家如果真的理解了上面說明的理論, 那麼應該沒有問題來理解這些code。

當然有任何問題大家可以在留言區回覆我 ,歡迎大家指正!

4種遍歷

  • 前序遍歷 根左右
  • 中序遍歷 左跟右
  • 後序遍歷 左右根
  • 層級遍歷 從root開始,一層層
/***java
     * 前序遍歷
     * 1-根節點
     * 2-左節點
     * 3-右節點
     * 根左右
     * @param parent
     */
    private void xianxu(AvlNodeInteger parent){
        System.out.println(parent.getValue());
        if(null != parent.getLeft()){
            xianxu(parent.getLeft());
        }
        if(null != parent.getRight()){
            xianxu(parent.getRight());
        }
    }

 /***
     * 中序遍歷
     * 左節點
     * 根節點
     * 右節點
     * 
     * 左根右
     * @param parent
     */
    private void zhongxu(AvlNodeInteger parent){
        if(null != parent.getLeft()){
            zhongxu(parent.getLeft());
        }
        System.out.println(parent.getValue());

        if(null != parent.getRight()){
            zhongxu(parent.getRight());
        }
    }


    /***
     * 後續遍歷
     * 左右根
     * 左節點
     * 右節點
     * 根節點
     */
    private void houxu(AvlNodeInteger parent){
        if(null != parent.getLeft()){
            houxu(parent.getLeft());
        }
        if(null != parent.getRight()){
            houxu(parent.getRight());
        }
        System.out.println(parent);
    }


    /***
     * 層級遍歷
     * @param parent
     */
    private void cengji(List<AvlNodeInteger> parent){
        if(null == parent || parent.size() == 0) return;

        //打印當前層
        List<AvlNodeInteger> AvlNodeIntegers = new ArrayList<AvlNodeInteger>();
        int k = 0;
        for(int i = 0 ; i < parent.size() ; i++){
            AvlNodeInteger currentNode = parent.get(i);
            System.out.println(currentNode.getValue()+",");
            if(null != currentNode.getLeft()){
                AvlNodeIntegers.add(currentNode.getLeft());
                k++;
            }
            if(null != currentNode.getRight()){
                AvlNodeIntegers.add(currentNode.getRight());
                k++;
            }
        }
        System.out.println("--------------------------");
        cengji(AvlNodeIntegers);
    }

完整源碼見:平衡二叉樹 AVL樹結構詳解 [Java實現]--源碼部分

END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章