數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹(三)平衡二叉樹原理及實現(xiàn)

引言

前兩篇文章中,我們學(xué)習(xí)了二叉樹的結(jié)構(gòu)、性質(zhì)、實現(xiàn)以及應(yīng)用,它的操作時間復(fù)雜度為O(lgn),但注意這個復(fù)雜度從嚴(yán)格意義上來說不太準(zhǔn)確,在某一棵子樹子樹的深度遠(yuǎn)遠(yuǎn)大于另外一顆子樹時,查找效率會上升到O(n),如下面的情況:


二叉樹特例-單枝二叉樹(圖片來自澤堅博客).png

從上圖可以看出,按照排序數(shù)組構(gòu)建二叉樹時,它變成了單向的二叉樹,本質(zhì)和鏈表沒什么區(qū)別,操作復(fù)雜度為O(n)。這個問題該如何解決呢?我們引入"子樹平衡"這一性質(zhì),來保證左右的葉子節(jié)點不會"埋"的太深,這種樹被稱為平衡二叉樹。
平衡二叉樹具有如下性質(zhì):
1.具備二叉排序樹的所有性質(zhì);
2.左右子樹的高度相差絕對值不大于1;
3.左右子樹均為平衡二叉樹.
本文我們規(guī)定葉子節(jié)點的高度為0,空節(jié)點高度為-1,父節(jié)點高度為左右子樹中較大高度+1。

二叉樹的失衡情況分析

當(dāng)左右子樹的高度相差大于等于2時,表示該根節(jié)點失衡,共有四種失衡情況:
① 左子樹的左子樹高度-左子樹的右子樹高度>=2,簡稱為LL型;
②左子樹的右子樹高度-左子樹的左子樹高度>=2,簡稱LR型;
③右子樹的左子樹高度-右子樹的右子樹高度>=2,簡稱為RL型;
④ 右子樹的右子樹高度-右子樹的左子樹高度>=2,簡稱RR型;
其中① 和④ 為一對,需要做但旋轉(zhuǎn);②和③為一對,需要做雙旋轉(zhuǎn)。下面詳細(xì)分析。
先看看節(jié)點結(jié)構(gòu),和搜索樹相比,多了高度字段,用于平衡性判斷。

package tree.avltree;

/**
 * Created by chenming on 2018/6/5
 * 平衡二叉樹節(jié)點,多了高度字段
 */
public class AVLNode<T extends Comparable> {
    public AVLNode<T> left;//左結(jié)點

    public AVLNode<T> right;//右結(jié)點

    public T data;

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

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

    public AVLNode(AVLNode<T> left, AVLNode<T> right, T data) {
        this(left, right, data, 0);
    }

    public AVLNode(AVLNode<T> left, AVLNode<T> right, T data, int height) {
        this.left = left;
        this.right = right;
        this.data = data;
        this.height = height;
    }

    /**
     * 判斷是否為葉子結(jié)點
     *
     * @return
     */
    public boolean isLeaf() {
        return this.left == null && this.right == null;
    }
}

LL型單旋

LL型單旋如下圖圖1:


LL單旋.png

當(dāng)x的左子樹w的左子樹高度為2,而x的右子樹高度為1,導(dǎo)致x節(jié)點失衡,需要以x為中心做右旋操作,結(jié)構(gòu)變成圖2,w變成根節(jié)點,x變成w的右子樹,w的右子樹掛載到x的左邊,二叉樹重新平衡。旋轉(zhuǎn)操作后,只需要更新w與x的高度即可。LL右旋代碼如下:

    /**
     * 向左子樹的左節(jié)點插入元素,執(zhí)行右旋操作
     *
     * @param x
     * @return
     */
    private AVLNode<T> singleRotateLeft(AVLNode<T> x) {
        AVLNode<T> w = x.left;
        x.left = w.right;
        w.right = x;
        //重新計算x w的高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        w.height = Math.max(height(w.left), x.height) + 1;
        return w;
    }

RR型單旋

RR型號單旋如下圖1:


RR型單旋.png

RR型與LL型正好對稱,x的右子樹導(dǎo)致失衡,需要將x左旋,旋轉(zhuǎn)后結(jié)構(gòu)如圖2.x為根節(jié)點,w為x的左子樹,x的左子樹掛載到w的右子樹上。此時,樹重新獲得平衡。然后更新w和x的高度即可。代碼如下:

   /**
     * 向右子樹的右節(jié)點插入元素,執(zhí)行左旋操作
     *
     * @param w
     * @return
     */
    private AVLNode<T> singleRotateRight(AVLNode<T> w) {
        AVLNode<T> x = w.right;

        w.right = x.left;
        x.left = w;

        //重新計算x/w的高度
        w.height = Math.max(height(w.left), height(w.right)) + 1;
        x.height = Math.max(height(x.left), w.height) + 1;

        //返回新的根結(jié)點
        return x;
    }

RL和LR雙旋轉(zhuǎn)

對于RL和LR,僅僅做單旋轉(zhuǎn)無法恢復(fù)平衡,看下圖:


LR雙旋操作.png

Y的高度為2,僅做LL單旋的話,如圖2,無法恢復(fù)平衡,此時需要雙旋轉(zhuǎn)實現(xiàn)平衡,先處理好w子樹,將Y做左旋轉(zhuǎn),然后再對Y做右旋轉(zhuǎn).操作流程如下圖:


LR雙旋轉(zhuǎn).png

先將y以w為中心做右旋,然后將y以x為中心做右旋。雙旋后的結(jié)構(gòu)如圖3,根節(jié)點為y,它的子樹被拆分到兩個子樹w和x上,實現(xiàn)樹的"扁平化"。由于是基于單旋操作,代碼實現(xiàn)比較簡單:
    /**
     * 往左子樹的右孩子插入節(jié)點,左右雙旋
     */
    private AVLNode<T> doubleRotateWithLeft(AVLNode<T> x) {
        //w先進行右右單旋轉(zhuǎn)
        x.left = singleRotateRight(x.left);
        //x進行左左但旋
        return singleRotateLeft(x);
    }

相對應(yīng)的LR雙旋轉(zhuǎn)操作流程如下圖:


RL雙旋.png

旋轉(zhuǎn)原理和LR一樣,這里不再廢話了,大家看圖就能明白。代碼:

    /**
     * 右左旋轉(zhuǎn)(RL旋轉(zhuǎn))
     *
     * @param x
     * @return
     */
    private AVLNode<T> doubleRotateWithRight(AVLNode<T> x) {
        //先進行LL旋轉(zhuǎn)
        x.right = singleRotateLeft(x.right);
        //再進行RR旋轉(zhuǎn)
        return singleRotateRight(x);
    }

好了,本篇最核心的內(nèi)容分析完畢,下面分析平衡二叉樹的構(gòu)建和刪除操作,遵循下面兩條原則:
1>判斷平衡性,如果不平衡需要走2>
2>分析操作節(jié)點屬于哪種旋轉(zhuǎn)類型;
3>操作完成后,別忘記更新新節(jié)點高度.

平衡二叉樹的構(gòu)建

    /**
     * 平衡二叉樹的插入操作
     *
     * @param data
     * @param p
     * @return
     */
    private AVLNode<T> insert(T data, AVLNode<T> p) {
        if (p == null) {
            p = new AVLNode<>(data);
        } else if (data.compareTo(p.data) < 0) {//向左子樹尋找插入位置{
            p.left = insert(data, p.left);
            if (height(p.left) - height(p.right) == 2) {//子樹高度差為2,需要平衡
                if (data.compareTo(p.left.data) < 0) {//往左邊插入LL單旋轉(zhuǎn)
                    p = singleRotateLeft(p);
                } else {
                    p = doubleRotateWithLeft(p);//往左邊插入LR雙旋轉(zhuǎn)
                }
            }
        } else if (data.compareTo(p.data) > 0) {//向右子樹尋找插入位置
            p.right = insert(data, p.right);

            if (height(p.right) - height(p.left) == 2) {
                if (data.compareTo(p.right.data) < 0) {
                    //進行RL雙旋轉(zhuǎn)
                    p = doubleRotateWithRight(p);
                } else {
                    //進行RR單旋
                    p = singleRotateRight(p);
                }
            }
        }
        //重新計算節(jié)點高度
        if (p != null) {
            p.height = Math.max(height(p.left), height(p.right)) + 1;
        }
        return p;
    }

平衡二叉樹的插入操作是在搜索二叉樹的基礎(chǔ)上,判斷插入后的新節(jié)點的平衡性,如果不平衡,則判斷是四種情況的哪一種,做相應(yīng)操作,最后更新旋轉(zhuǎn)后的新根節(jié)點的高度。

平衡二叉樹的刪除操作

平衡二叉樹的刪除相對搜索二叉樹的刪除更為復(fù)雜(搜索二叉樹的刪除操作也不簡單,哭了。。),這里平衡二叉樹的刪除有一個特征:待刪除節(jié)點左子樹高度大于右子樹高度,則找在左子樹中尋找前驅(qū)節(jié)點替換當(dāng)前節(jié)點后,它仍為平衡二叉樹,反之則用右子樹的后繼節(jié)點替換它。和插入操作類似,做遞歸刪除后需要判斷刪除后新節(jié)點的平衡性,如果不平衡則判斷是四種情況的哪一種,做相應(yīng)操作,最后更新新子樹的高度即可。刪除代碼不做詳細(xì)解釋了,比較復(fù)雜,代碼注釋應(yīng)該比較清晰了。下面的代碼在搜索二叉樹的基礎(chǔ)上,借鑒了這篇博客中刪除節(jié)點的思路https://www.cnblogs.com/Camilo/p/3917041.html,目前親測有效,如有誤人子弟,歡迎打臉,還望見諒!
刪除節(jié)點代碼:

    /**
     * 非平衡二叉樹的刪除步驟:
     * ① 如果要刪除的結(jié)點q恰好是葉子結(jié)點,那么它可以立即被刪除
     * ②如果要刪除的結(jié)點q擁有一個孩子結(jié)點,則應(yīng)該調(diào)整要被刪除的
     * 父結(jié)點(p.left 或 p.right)指向被刪除結(jié)點的孩子結(jié)點(q.left 或 q.right)
     * ③如果要刪除的結(jié)點q擁有兩個孩子結(jié)點,則刪除策略是用q的右子樹的最小的數(shù)據(jù)替代要被刪除結(jié)點的數(shù)據(jù),
     * 并遞歸刪除用于替換的結(jié)點(此時該結(jié)點已為空),此時二叉查找樹的結(jié)構(gòu)并不會被打亂,其特性仍舊生效。
     * 采用這樣策略的主要原因是右子樹的最小結(jié)點的數(shù)據(jù)替換要被刪除的結(jié)點后可以滿足維持二叉查找樹的結(jié)構(gòu)和特性,
     * 又因為右子樹最小結(jié)點不可能有左孩子,刪除起來也相對簡單些。
     * 平衡二叉樹的刪除方法在之前的基礎(chǔ)上需要考慮下面兩點
     * ① 當(dāng)前待刪除節(jié)點左子樹高度大于右子樹高度,則找在左子樹中尋找前驅(qū)節(jié)點替換當(dāng)前節(jié)點
     * ② 刪除操作執(zhí)行后,別忘了重新更新根節(jié)點高度,然后根據(jù)左右子樹的高度判斷平衡性,根據(jù)前面分析的四種情況進行旋轉(zhuǎn)
     *
     * @param data
     * @param rootNode 當(dāng)前操作節(jié)點
     * @return
     */
    public AVLNode<T> removeAvlNode(T data, AVLNode<T> rootNode) {
        if (data == null) {
            return rootNode;
        }

        if (rootNode == null) {
            return rootNode;//沒有找到
        }

        int compareResult = data.compareTo(rootNode.data);
        if (compareResult < 0) {//左子樹遞歸刪除
            rootNode.left = removeAvlNode(data, rootNode.left);
            // 高度計算,判斷是否失衡
            //刪除后,修改樹的高度,左子樹刪除,
            rootNode.height = Math.max(height(rootNode.left), height(rootNode.right)) + 1;
            //左子樹刪除后,判斷是否失衡
            if (height(rootNode.right) - height(rootNode.left) == 2) {
                //調(diào)整右子樹
                if (height(rootNode.right.left) > height(rootNode.right.right)) {
                    //右子樹的左子樹導(dǎo)致失衡,則進行右左雙旋轉(zhuǎn)
                    rootNode = doubleRotateWithRight(rootNode);
                } else {
                    //右子樹的右子樹導(dǎo)致失衡,則進行右右單旋轉(zhuǎn)
                    rootNode = singleRotateRight(rootNode);
                }
            }
        } else if (compareResult > 0) {//右子樹遞歸刪除
            rootNode.right = removeAvlNode(data, rootNode.right);
            //高度計算,判斷是否失衡
            //刪除后,修改樹的高度
            rootNode.height = Math.max(height(rootNode.left), height(rootNode.right)) + 1;
            //右子樹刪除后,判斷rootNode是否失衡
            if (height(rootNode.left) - height(rootNode.right) == 2) {
                //調(diào)整右子樹
                if (height(rootNode.left.left) > height(rootNode.left.right)) {
                    //左子樹的左子樹導(dǎo)致失衡,則進行左左單旋轉(zhuǎn)
                    rootNode = singleRotateLeft(rootNode);
                } else {
                    //左子樹的右子樹導(dǎo)致失衡,則進行左右單旋轉(zhuǎn)
                    rootNode = doubleRotateWithLeft(rootNode);
                }
            }
        } else if (rootNode.left != null && rootNode.right != null) {//倆孩子節(jié)點
            //當(dāng)待刪除結(jié)點左子樹的高度大于右子樹的高度時,用*T的前驅(qū)結(jié)點pre代替*T,
            //再將結(jié)點pre從樹中刪除。這樣可以保證刪除結(jié)點后的樹仍為二叉平衡樹。
            if (height(rootNode.left) > height(rootNode.right)) {//找前驅(qū)節(jié)點替換
                rootNode.data = findMax(rootNode.left).data;
                rootNode.left = removeAvlNode(rootNode.data, rootNode.left);
            } else {//后繼節(jié)點替換
                //右子樹的最小值替換當(dāng)前節(jié)點值
                rootNode.data = findMin(rootNode.right).data;
                //移除用于替換的點
                rootNode.right = removeAvlNode(rootNode.data, rootNode.right);
            }

        } else {//只有一個孩子節(jié)點或者葉子節(jié)點的情況,次處返回的節(jié)點返回給上次遞歸,用于父節(jié)點鏈接該節(jié)點
            rootNode = (rootNode.left != null) ? rootNode.left : rootNode.right;
        }
        return rootNode;
    }

    /**
     * 查找最小值結(jié)點
     *
     * @param p
     * @return
     */
    private AVLNode<T> findMin(AVLNode<T> p) {
        if (p == null)//結(jié)束條件
            return null;
        else if (p.left == null)//如果沒有左結(jié)點,那么t就是最小的
            return p;
        return findMin(p.left);
    }

    /**
     * 查找最大值結(jié)點
     *
     * @param p
     * @return
     */
    private AVLNode<T> findMax(AVLNode<T> p) {
        if (p == null)
            return null;
        else if (p.right == null)//如果沒有右結(jié)點,那么t就是最大的
            return p;
        return findMax(p.right);
    }

    /**
     * @param p
     * @return
     */
    public int height(AVLNode<T> p) {
        return p == null ? -1 : p.height;
    }

完整代碼

除了插入元素和刪除,其他方法與搜索二叉樹基本一樣,不再說明。

package tree.avltree;

import queue.Queue;
import tree.BinaryNode;
import tree.Tree;

/**
 * Created by chenming on 2018/6/5
 */
public class AVLTree<T extends Comparable> implements Tree<T> {
    public AVLNode<T> mRoot;//為了測試用,mRoot先暴露出去

    /**
     * 向左子樹的左節(jié)點插入元素,執(zhí)行右旋操作
     *
     * @param x
     * @return
     */
    private AVLNode<T> singleRotateLeft(AVLNode<T> x) {
        AVLNode<T> w = x.left;
        x.left = w.right;
        w.right = x;
        //重新計算x w的高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        w.height = Math.max(height(w.left), x.height) + 1;
        return w;
    }

    /**
     * 向右子樹的右節(jié)點插入元素,執(zhí)行左旋操作
     *
     * @param w
     * @return
     */
    private AVLNode<T> singleRotateRight(AVLNode<T> w) {
        AVLNode<T> x = w.right;

        w.right = x.left;
        x.left = w;

        //重新計算x/w的高度
        w.height = Math.max(height(w.left), height(w.right)) + 1;
        x.height = Math.max(height(x.left), w.height) + 1;

        //返回新的根結(jié)點
        return x;
    }

    /**
     * 往左子樹的右孩子插入節(jié)點,左右雙旋
     */
    private AVLNode<T> doubleRotateWithLeft(AVLNode<T> x) {
        //w先進行右右單旋轉(zhuǎn)
        x.left = singleRotateRight(x.left);
        return singleRotateLeft(x);
    }

    /**
     * 右左旋轉(zhuǎn)(RL旋轉(zhuǎn))
     *
     * @param x
     * @return
     */
    private AVLNode<T> doubleRotateWithRight(AVLNode<T> x) {
        //先進行LL旋轉(zhuǎn)
        x.right = singleRotateLeft(x.right);
        //再進行RR旋轉(zhuǎn)
        return singleRotateRight(x);
    }

    @Override
    public void insert(T data) {
        mRoot = insert(data, mRoot);
    }

    /**
     * 平衡二叉樹的插入操作
     *
     * @param data
     * @param p
     * @return
     */
    private AVLNode<T> insert(T data, AVLNode<T> p) {
        if (p == null) {
            p = new AVLNode<>(data);
        } else if (data.compareTo(p.data) < 0) {//向左子樹尋找插入位置{
            p.left = insert(data, p.left);
            if (height(p.left) - height(p.right) == 2) {//子樹高度差為2,需要平衡
                if (data.compareTo(p.left.data) < 0) {//往左邊插入LL單旋轉(zhuǎn)
                    p = singleRotateLeft(p);
                } else {
                    p = doubleRotateWithLeft(p);//往左邊插入LR雙旋轉(zhuǎn)
                }
            }
        } else if (data.compareTo(p.data) > 0) {//向右子樹尋找插入位置
            p.right = insert(data, p.right);

            if (height(p.right) - height(p.left) == 2) {
                if (data.compareTo(p.right.data) < 0) {
                    //進行RL雙旋轉(zhuǎn)
                    p = doubleRotateWithRight(p);
                } else {
                    //進行RR單旋
                    p = singleRotateRight(p);
                }
            }
        }
        //重新計算節(jié)點高度
        if (p != null) {
            p.height = Math.max(height(p.left), height(p.right)) + 1;
        }
        return p;
    }

    /**
     * 刪除操作
     *
     * @param data
     */
    @Override
    public void remove(T data) {
//        mRoot = remove(mRoot, data);
        mRoot = removeAvlNode(data, mRoot);
    }

    /**
     * 非平衡二叉樹的刪除步驟:
     * ① 如果要刪除的結(jié)點q恰好是葉子結(jié)點,那么它可以立即被刪除
     * ②如果要刪除的結(jié)點q擁有一個孩子結(jié)點,則應(yīng)該調(diào)整要被刪除的
     * 父結(jié)點(p.left 或 p.right)指向被刪除結(jié)點的孩子結(jié)點(q.left 或 q.right)
     * ③如果要刪除的結(jié)點q擁有兩個孩子結(jié)點,則刪除策略是用q的右子樹的最小的數(shù)據(jù)替代要被刪除結(jié)點的數(shù)據(jù),
     * 并遞歸刪除用于替換的結(jié)點(此時該結(jié)點已為空),此時二叉查找樹的結(jié)構(gòu)并不會被打亂,其特性仍舊生效。
     * 采用這樣策略的主要原因是右子樹的最小結(jié)點的數(shù)據(jù)替換要被刪除的結(jié)點后可以滿足維持二叉查找樹的結(jié)構(gòu)和特性,
     * 又因為右子樹最小結(jié)點不可能有左孩子,刪除起來也相對簡單些。
     * 平衡二叉樹的刪除方法在之前的基礎(chǔ)上需要考慮下面兩點
     * ① 當(dāng)前待刪除節(jié)點左子樹高度大于右子樹高度,則找在左子樹中尋找前驅(qū)節(jié)點替換當(dāng)前節(jié)點
     * ② 刪除操作執(zhí)行后,別忘了重新更新根節(jié)點高度,然后根據(jù)左右子樹的高度判斷平衡性,根據(jù)前面分析的四種情況進行旋轉(zhuǎn)
     *
     * @param data
     * @param rootNode 當(dāng)前操作節(jié)點
     * @return
     */
    public AVLNode<T> removeAvlNode(T data, AVLNode<T> rootNode) {
        if (data == null) {
            return rootNode;
        }

        if (rootNode == null) {
            return rootNode;//沒有找到
        }

        int compareResult = data.compareTo(rootNode.data);
        if (compareResult < 0) {//左子樹遞歸刪除
            rootNode.left = removeAvlNode(data, rootNode.left);
            // 高度計算,判斷是否失衡
            //刪除后,修改樹的高度,左子樹刪除,
            rootNode.height = Math.max(height(rootNode.left), height(rootNode.right)) + 1;
            //左子樹刪除后,判斷是否失衡
            if (height(rootNode.right) - height(rootNode.left) == 2) {
                //調(diào)整右子樹
                if (height(rootNode.right.left) > height(rootNode.right.right)) {
                    //右子樹的左子樹導(dǎo)致失衡,則進行右左雙旋轉(zhuǎn)
                    rootNode = doubleRotateWithRight(rootNode);
                } else {
                    //右子樹的右子樹導(dǎo)致失衡,則進行右右單旋轉(zhuǎn)
                    rootNode = singleRotateRight(rootNode);
                }
            }
        } else if (compareResult > 0) {//右子樹遞歸刪除
            rootNode.right = removeAvlNode(data, rootNode.right);
            //高度計算,判斷是否失衡
            //刪除后,修改樹的高度
            rootNode.height = Math.max(height(rootNode.left), height(rootNode.right)) + 1;
            //右子樹刪除后,判斷rootNode是否失衡
            if (height(rootNode.left) - height(rootNode.right) == 2) {
                //調(diào)整右子樹
                if (height(rootNode.left.left) > height(rootNode.left.right)) {
                    //左子樹的左子樹導(dǎo)致失衡,則進行左左單旋轉(zhuǎn)
                    rootNode = singleRotateLeft(rootNode);
                } else {
                    //左子樹的右子樹導(dǎo)致失衡,則進行左右單旋轉(zhuǎn)
                    rootNode = doubleRotateWithLeft(rootNode);
                }
            }
        } else if (rootNode.left != null && rootNode.right != null) {//倆孩子節(jié)點
            //當(dāng)待刪除結(jié)點左子樹的高度大于右子樹的高度時,用*T的前驅(qū)結(jié)點pre代替*T,
            //再將結(jié)點pre從樹中刪除。這樣可以保證刪除結(jié)點后的樹仍為二叉平衡樹。
            if (height(rootNode.left) > height(rootNode.right)) {//找前驅(qū)節(jié)點替換
                rootNode.data = findMax(rootNode.left).data;
                rootNode.left = removeAvlNode(rootNode.data, rootNode.left);
            } else {//后繼節(jié)點替換
                //右子樹的最小值替換當(dāng)前節(jié)點值
                rootNode.data = findMin(rootNode.right).data;
                //移除用于替換的點
                rootNode.right = removeAvlNode(rootNode.data, rootNode.right);
            }

        } else {//只有一個孩子節(jié)點或者葉子節(jié)點的情況,次處返回的節(jié)點返回給上次遞歸,用于父節(jié)點鏈接該節(jié)點
            rootNode = (rootNode.left != null) ? rootNode.left : rootNode.right;
        }
        return rootNode;
    }

    /**
     * 查找最小值結(jié)點
     *
     * @param p
     * @return
     */
    private AVLNode<T> findMin(AVLNode<T> p) {
        if (p == null)//結(jié)束條件
            return null;
        else if (p.left == null)//如果沒有左結(jié)點,那么t就是最小的
            return p;
        return findMin(p.left);
    }

    /**
     * 查找最大值結(jié)點
     *
     * @param p
     * @return
     */
    private AVLNode<T> findMax(AVLNode<T> p) {
        if (p == null)
            return null;
        else if (p.right == null)//如果沒有右結(jié)點,那么t就是最大的
            return p;
        return findMax(p.right);
    }

    /**
     * @param p
     * @return
     */
    public int height(AVLNode<T> p) {
        return p == null ? -1 : p.height;
    }

    @Override
    public boolean isEmpty() {
        return mRoot == null;
    }

    @Override
    public int size() {
        return size(mRoot);
    }

    /**
     * 遞歸計算左右子樹的和,再加上當(dāng)前節(jié)點
     *
     * @param currentNode
     * @return
     */
    public int size(AVLNode<T> currentNode) {
        if (currentNode == null) {
            return 0;
        }
        int leftSize = size(currentNode.left);
        int rightSize = size(currentNode.right);
        return leftSize + rightSize + 1;
    }


    @Override
    public int height() {
        return mRoot == null ? 0 : mRoot.height;
    }

    @Override
    public String preOrder() {
        return preOrder(mRoot);
    }

    /**
     * 前序遍歷,先遍歷節(jié)點,再遍歷左子樹,最后遍歷右子樹
     *
     * @return
     */
    public String preOrder(AVLNode<T> root) {
        StringBuilder sb = new StringBuilder();
        if (root != null) {
            sb.append(root.data + ", ");
            sb.append(preOrder(root.left));
            sb.append(preOrder(root.right));
        }
        return sb.toString();
    }

    @Override
    public String inOrder() {
        return inOrder(mRoot);
    }

    /**
     * 遞歸中序遍歷
     *
     * @param root
     * @return
     */
    public String inOrder(AVLNode<T> root) {
        StringBuilder sb = new StringBuilder();
        if (root != null) {
            sb.append(inOrder(root.left));
            sb.append(root.data + ", ");
            sb.append(inOrder(root.right));
        }
        return sb.toString();
    }


    @Override
    public String postOrder() {
        return postOrder(mRoot);
    }

    /**
     * 遞歸中序遍歷
     *
     * @param root
     * @return
     */
    public String postOrder(AVLNode<T> root) {
        StringBuilder sb = new StringBuilder();
        if (root != null) {
            sb.append(postOrder(root.left));
            sb.append(postOrder(root.right));
            sb.append(root.data + ", ");
        }
        return sb.toString();
    }

    /**
     * 層遍歷:關(guān)鍵在于隊列保存節(jié)點的兩個孩子節(jié)點,每次迭代做如下操作:
     * 1.將非空子節(jié)點入隊
     * 2.輸出隊列頭節(jié)點
     *
     * @return
     */
    @Override
    public String levelOrder() {
        return levelOrder(mRoot);
    }

    /**
     * 層遍歷
     *
     * @param node
     * @return
     */
    public String levelOrder(AVLNode<T> node) {
        StringBuilder sb = new StringBuilder();
        Queue<AVLNode<T>> queue = new Queue<>();
        while (node != null) {
            sb.append(node.data + ", ");
            AVLNode<T> leftNode = node.left;
            AVLNode<T> rightNode = node.right;
            //同層的子節(jié)點入隊
            if (leftNode != null) {
                queue.enquene(leftNode);
            }
            if (rightNode != null) {
                queue.enquene(rightNode);
            }

            node = queue.dequeue();//遍歷下個節(jié)點

        }

        return sb.toString();
    }


    @Override
    public T findMin() {
        return findMin(mRoot).data;
    }

    @Override
    public T findMax() {
        return findMax(mRoot).data;
    }

    @Override
    public BinaryNode findNode(T data) {
        //@see SearchTree中的實現(xiàn)
        //return findNode(T data, mRoot)
        return null;
    }

    /**
     * 遞歸查找元素
     *
     * @param data
     * @param currentNode
     * @return
     */
    public AVLNode findNode(T data, AVLNode<T> currentNode) {
        if (currentNode == null) {
            return null;
        }

        int compareResult = data.compareTo(currentNode.data);
        if (compareResult == 0) {
            return currentNode;
        }

        if (compareResult > 0) {
            currentNode = findNode(data, currentNode.right);//移動node指針
        } else if (compareResult < 0) {
            currentNode = findNode(data, currentNode.left);
        }

        return currentNode;
    }

    @Override
    public boolean contains(T data) {
        return contains(data, mRoot);
    }

    /**
     * 遞歸實現(xiàn)包含
     *
     * @param data
     * @param tree
     * @return
     */
    public boolean contains(T data, AVLNode<T> tree) {
        if (data == null) {
            return false;
        }

        if (tree == null) {
            return false;
        }

        int compareResult = data.compareTo(tree.data);
        if (compareResult == 0) {
            return true;
        } else if (compareResult > 0) {
            return contains(data, tree.right);
        } else if (compareResult < 0) {
            return contains(data, tree.left);
        }
        return false;
    }

    @Override
    public void clear() {
        mRoot = null;
    }
}

最后獻上測試代碼:

@Test
    public void testAVLTree() {
        Integer[] avlArr = {9, 8, 7, 6, 5, 4, 3, 2, 1};
        AVLTree<Integer> avlTree = new AVLTree<>();
        for (int i = 0; i < avlArr.length; i++) {
            avlTree.insert(avlArr[i]);
        }
        System.out.println("=====平衡二叉樹輸出=====");
        dumpAvlTree(avlTree.mRoot);//樹結(jié)構(gòu)打印測試
        avlTree.remove(6);
        System.out.println("=====平衡二叉樹深度=====");
        System.out.println(avlTree.height());
        System.out.println("=====平衡二叉樹大小=====");
        System.out.println(avlTree.size());
        System.out.println("=====平衡二叉樹刪除元素后輸出=====");
        dumpAvlTree(avlTree.mRoot);
        System.out.println("=====平衡二叉樹深度=====");
        System.out.println(avlTree.height());
    }

    /**
     * 打印AVL樹信息
     *
     * @param root
     */
    public void dumpAvlTree(AVLNode<Integer> root) {
        if (root == null) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(root.data);
        sb.append("(");
        if (root.left != null && root.right != null) {
            sb.append(root.left.data + "-" + root.left.height);
            sb.append(" , ");
            sb.append(root.right.data + "-" + root.right.height);
        } else if (root.left != null) {
            sb.append(root.left.data + "-" + root.left.height);
            sb.append(" , ");
        } else if (root.right != null) {
            sb.append(" , ");
            sb.append(root.right.data + "-" + root.right.height);
        }
        sb.append(")");
        System.out.println(sb.toString());

        dumpAvlTree(root.left);
        dumpAvlTree(root.right);
    }

測試結(jié)果如下:

10101.png

完整代碼地址:數(shù)據(jù)結(jié)構(gòu)與算法學(xué)習(xí)JAVA描述GayHub地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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