數據結構與算法之AVL平衡樹

AVL平衡樹是計算機科學中是最先發明的自平衡二叉查找樹。
最簡單的想法便是要求左右子樹具有相同的高度。這種理想的平衡樹太過於嚴格
而難以實現
所以我們必須對其放寬條件。只要其左右子樹的高度相差不超過1即可

既然條件放寬了,那我們如何實現呢?
這裏就要引入平衡樹的一個重要思想那就是調整思想
當我們在按照二叉搜索樹的插入方法插入時。如果這個二叉樹不滿足AVL樹的條件
這時候就該對其調整使其滿足AVL樹的條件

如何調整?

AVL樹的基本操作一般涉及運做同在不平衡的二叉查找樹所運做的同樣的算法。但是要進行預先或隨後做一次或多次所謂的“AVL 旋轉”
假設由於在二叉排序樹上插入結點而失去平衡的最小子樹根結點爲a(即a是離插入點最近,且平衡因子絕對值超過1的祖先結點),則失去平衡後進行進行的規律可歸納爲下列四種情況:

單向右旋平衡處理RR:由於在a的左子樹根結點的左子樹上插入結點,a的平衡因子由1增至2,致使以a爲根的子樹失去平衡,則需進行一次右旋轉操作;

單向左旋平衡處理LL:由於在a的右子樹根結點的右子樹上插入結點,a的平衡因子由-1變爲-2,致使以a爲根的子樹失去平衡,則需進行一次左旋轉操作;

雙向旋轉(先左後右)平衡處理LR:由於在a的左子樹根結點的右子樹上插入結點,a的平衡因子由1增至2,致使以a爲根的子樹失去平衡,則需進行兩次旋轉(先左旋後右旋)操作。

雙向旋轉(先右後左)平衡處理RL:由於在a的右子樹根結點的左子樹上插入結點,a的平衡因子由-1變爲-2,致使以a爲根的子樹失去平衡,則需進行兩次旋轉(先右旋後左旋)操作。

瞭解了這麼多的二叉樹的方法

我這以Java語言對其實現

AVL樹的節點重點內容

public class AvlNode<T> {
    public AvlNode(T date) {
        this(date, null, null);
    }

    public AvlNode(T date, AvlNode<T> left, AvlNode<T> right) {
        super();
        this.date = date;
        this.left = left;
        this.right = right;
        this.hight = 0;
    }

    T date;
    AvlNode<T> left;
    AvlNode<T> right;
    int hight; // 該節點的高度
}

AVL樹的實現

ublic class AvlTree<T extends Comparable<? super T>> {

    /*
     * 節點高度
     */
    public int getNodeHight(AvlNode<T> node) {
        return node == null ? 0 : node.hight;
    }

    /*
     * 將節點按照二叉搜索樹插入 然後進行調整
     */
    public AvlNode<T> insert(AvlNode<T> node, T x) {
        // 遞歸出口
        if (node == null) {
            return new AvlNode<T>(x);
        }
        int result = x.compareTo(node.date);
        // 二叉搜索樹 左邊節點小於根節點 右邊節點大於根節點
        if (result < 0) {
            node.left = insert(node.left, x);
        } else if (result > 0)
            node.right = insert(node.right, x);
        else
            ;
        return balance(node);
    }

    // 允許子樹之間的高度差爲1
    private static final int ALLOWED_IMBALANCE = 1;

    private AvlNode<T> balance(AvlNode<T> node) {
        if (node == null)
            return node;
        // 如果左子樹比右子樹高度超過一
        if (getNodeHight(node.left) - getNodeHight(node.right) > ALLOWED_IMBALANCE)
            // 如果左子樹的左子樹的高度大於等於右子樹的高度則向左旋轉
            if (getNodeHight(node.left.left) >= getNodeHight(node.left.right))
                node = rotateWithLeftChild(node);
            else
                // 如果左子樹的右子樹的高度大於左子樹的高度則向雙向右旋轉
                node = doubleWithRightChild(node);
        // 如果右子樹比左子樹高度超過一
        else if (getNodeHight(node.right) - getNodeHight(node.left) > ALLOWED_IMBALANCE)
            // 如果右子樹的右子樹的高度大於等於左子樹的高度則向右旋轉
            if (getNodeHight(node.right.right) >= getNodeHight(node.right.left))
                node = rotateWithRightChild(node);
            else
                // 如果左子樹的右子樹的高度大於左子樹的高度則向雙向右旋轉
                node = doubleWithLeftChild(node);
        // 平衡完成後將高度更新
        node.hight = Math.max(getNodeHight(node.left), getNodeHight(node.right) + 1);
        return node;
    }

    // 單向右旋轉
    private AvlNode<T> rotateWithRightChild(AvlNode<T> node) {
        // 翻轉節點爲 node 和 node.left
        AvlNode<T> nodeR = node.right;
        // 向左翻轉
        nodeR.left = node;
        node.right = nodeR.left;
        // 先更新node高度因爲node的高度降低了
        node.hight = Math.max(getNodeHight(node.left), getNodeHight(node.right));
        // 更新nodeR nodeR高度上升了
        node.hight = Math.max(getNodeHight(nodeR.left), getNodeHight(nodeR.right));
        return nodeR;
    }

    // 雙向右旋轉
    private AvlNode<T> doubleWithRightChild(AvlNode<T> node) {
        // 所以先將節點的右樹向左旋轉使得其右樹大於等於左樹
        node.right = rotateWithLeftChild(node.right);
        // 然後便可以使用單向左旋轉
        return rotateWithLeftChild(node);
    }

    // 單向左旋轉
    private AvlNode<T> rotateWithLeftChild(AvlNode<T> node) {
        // 翻轉節點爲 node 和 node.left
        AvlNode<T> nodeL = node.left;
        // 向左翻轉
        nodeL.right = node;
        node.left = nodeL.right;
        // 先更新node高度因爲node的高度降低了
        node.hight = Math.max(getNodeHight(node.left), getNodeHight(node.right));
        // 更新nodeL nodeL高度上升了
        node.hight = Math.max(getNodeHight(nodeL.left), getNodeHight(nodeL.right));
        return nodeL;
    }

    // 雙向左旋轉
    private AvlNode<T> doubleWithLeftChild(AvlNode<T> node) {
        // 雙向左旋轉的條件是node的左子樹的左子樹小於右子樹
        // 左旋轉由於左旋轉會將原node的左子樹的右子樹給旋轉後的node的左子樹
        // 由於原node的左子樹的左子樹小於右子樹
        // node的高度=原node的左子樹的右子樹的高度+1大於旋轉後的左子樹
        // 所以先將節點的左樹向右旋轉使得其左樹大於等於右樹
        node.left = rotateWithRightChild(node.left);
        // 然後便可以使用單向左旋轉
        return rotateWithLeftChild(node);
    }

    // 移除一個節點
    public AvlNode<T> remove(T x, AvlNode<T> node) {
        // 未找到 
        if (node == null) {
            return node;
        }
        int result = x.compareTo(node.date);
        if (result < 0)
            node.left = remove(x, node.left);
        else if (result > 0)
            node.right = remove(x, node.right);
        //兩個子樹
        else if (node.left != null && node.right != null) {
            node.date = findMin(node.right);
            node.right = remove(node.date, node.right);
        } else
            //一個子樹
            node = node.left == null ? node.right : node.left;
        return balance(node);
    }

    private T findMin(AvlNode<T> node) {
        if (node == null) {
            return null;
        }
        // 遞歸出口
        else if (node.left == null)
            return node.date;
        return findMin(node.left);
    }

到此AVL平衡樹基本實現了。

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