數(shù)據(jù)結(jié)構(gòu)-AVL

AVL定義:

AVL的命名是由2個(gè)其發(fā)明者的名字組成的,G.M.Adelson-Velsky和E.M.Landis,兩個(gè)俄羅斯人。AVL既有平衡二叉樹的特性,又有二分搜索樹的特性。

平衡二叉樹:對(duì)于任意一個(gè)節(jié)點(diǎn),左子樹和右子樹的高度差不能超過1。

平衡二叉樹

二分搜索樹:
1、二分搜索樹的每個(gè)節(jié)點(diǎn)的值,大于其左子樹的所有節(jié)點(diǎn)的值;小于其右子樹的所有節(jié)點(diǎn)的值;
2、每一顆子樹也是二分搜索樹。

AVL結(jié)構(gòu):

因?yàn)锳VL是二分搜索樹,所以可以直接使用其結(jié)構(gòu),這里泛型使用了映射(Map),是為了符合更多的數(shù)據(jù)類型。然后新增了變量height,來表示當(dāng)前節(jié)點(diǎn)的高度,其中葉子節(jié)點(diǎn)的高度為1。如下圖:

public class AVLTree<K extends Comparable<K>, V>{

    private class Node{
        //當(dāng)前節(jié)點(diǎn)的值
        public K key;
        public V value;
        //左節(jié)點(diǎn)
        public Node left;
        //右節(jié)點(diǎn)
        public Node right;
        // 節(jié)點(diǎn)高度,葉子結(jié)點(diǎn)為1
        public int height;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            height = 1;
        }
    }

    private Node root;
    private int size;

獲取節(jié)點(diǎn)的高度和平衡因子:

平衡因子:左孩子的高度減去右孩子的高度。

1、獲取當(dāng)前節(jié)點(diǎn)的高度:

// 獲取節(jié)點(diǎn)node的高度
private int getHeight(Node node){
        if (node == null) return 0;
        return node.height;
   }

2、獲取當(dāng)前節(jié)點(diǎn)的平衡因子:

// 獲取節(jié)點(diǎn)node的平衡因子,平衡因子不在[-1,1]區(qū)間,說明不是平衡二叉樹
 private int getBalanceFactory(Node node){
        if (node == null) return 0;
        return getHeight(node.left) - getHeight(node.right);
   }

判斷該二叉樹是否是一顆二分搜索樹:

思路:因?yàn)槎炙阉鳂涞闹行虮闅v會(huì)使元素自然排序,于是我們可以利用這個(gè)特性。對(duì)當(dāng)前的二叉樹進(jìn)行中序遍歷,把值存入一個(gè)集合List中,然后遍歷這個(gè)List,看前一個(gè)元素是否小于后一個(gè)元素,如果有一個(gè)不滿足返回false,直到全部元素遍歷完才返回true。

// 判斷該二叉樹是否是一顆二分搜索樹
    public boolean isBST(){
        // 二分搜索樹的一個(gè)特性:中序遍歷的結(jié)果是自然排序的
        ArrayList<K> keys = new ArrayList<>();
        inOrder(root, keys);
        for (int i = 1; i < keys.size(); i++) {
            if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
                return false;
        }
        return true;
    }

    // 中序遍歷
    private void inOrder(Node node, ArrayList<K> keys) {
        if (node == null) return;

        inOrder(node.left, keys);
        keys.add(node.key);
        inOrder(node.right, keys);
    }

判斷該二叉樹是否是一顆平衡二叉樹:

思路:遍歷判斷當(dāng)前節(jié)點(diǎn)的平衡因子balanceFactory是否 > 1,遞歸的終止條件為:1、執(zhí)行到最后一個(gè)NULL元素,此時(shí)平衡因子為0,返回true;2、平衡因子>1,返回false。遞歸執(zhí)行,只有左右孩子同時(shí)滿足平衡二叉樹的特性時(shí)才為true。

// 判斷該二叉樹是否是一顆平衡二叉樹,看平衡因子
    public boolean isBalanced(){
        return isBalanced(root);
    }

    // 判斷node為根的二叉樹是否是一顆平衡二叉樹,遞歸算法
    private boolean isBalanced(Node node) {
        if (node == null) return true;
        int balanceFactory = getBalanceFactory(node);
        if (Math.abs(balanceFactory) > 1)
            return false;

        // 左右子樹遞歸都為true才行
        return isBalanced(node.left) && isBalanced(node.right);
    }

LL與AVL的右旋轉(zhuǎn):

在執(zhí)行了添加元素或者刪除元素之后,由于會(huì)對(duì)節(jié)點(diǎn)高度進(jìn)行修改,很可能破壞AVL的平衡性。于是需要左旋轉(zhuǎn)或者右旋轉(zhuǎn)操作來使AVL重新獲得平衡。
右旋轉(zhuǎn)的時(shí)機(jī):插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL)。
我們先看右旋轉(zhuǎn),如下圖:

右旋轉(zhuǎn)

首先從新增或者刪除的元素往上,找到第一個(gè)平衡因子=2的節(jié)點(diǎn),如上圖的 y,然后取出 x 的右子樹 T3,然后使 y 的左子樹 x 的右孩子指向 y,然后讓 y 的左子樹指向 T3。此時(shí) x 成為了新的AVL的根節(jié)點(diǎn),并且新的AVL即符合平衡二叉樹的特性又符合二分搜索樹的特性。

右旋轉(zhuǎn)代碼實(shí)現(xiàn):

    // 對(duì)節(jié)點(diǎn)y進(jìn)行向右旋轉(zhuǎn)操作,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
    //        y                              x
    //       / \                           /   \
    //      x   T4     向右旋轉(zhuǎn) (y)        z     y
    //     / \       - - - - - - - ->    / \   / \
    //    z   T3                       T1  T2 T3 T4
    //   / \
    // T1   T2
    private Node rightRotate(Node y){
        // 取出y 的左孩子x的右孩子T3
        Node x = y.left;
        Node T3 = x.right;
        x.right = y;
        y.left = T3;

        // 更新height
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
        return x;
    }

RR與AVL的左旋轉(zhuǎn):

左旋轉(zhuǎn)和右旋轉(zhuǎn)剛好相反,于是實(shí)現(xiàn)機(jī)制是一樣的。
左旋轉(zhuǎn)的時(shí)機(jī): 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR)。

左旋轉(zhuǎn)之前

左旋轉(zhuǎn)之后

左旋轉(zhuǎn)的代碼實(shí)現(xiàn):

    // 對(duì)節(jié)點(diǎn)y進(jìn)行向左旋轉(zhuǎn)操作,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
    //    y                             x
    //  /  \                          /   \
    // T4   x      向左旋轉(zhuǎn) (y)      y     z
    //     / \   - - - - - - - ->   / \   / \
    //   T3  z                     T4 T3 T1 T2
    //      / \
    //     T1 T2
    private Node leftRotate(Node y){
        // 取出y 的右孩子x的左孩子T3
        Node x = y.right;
        Node T3 = x.left;
        x.left = y;
        y.right = T3;

        // 更新height
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
        return x;
    }

LR:

插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR)。


LR

此時(shí)則需要讓不平衡節(jié)點(diǎn) y 的左孩子 x 進(jìn)行左旋轉(zhuǎn),然后讓不平衡節(jié)點(diǎn) y 的左子樹指向左旋轉(zhuǎn)之后的新的二叉樹的根節(jié)點(diǎn) z。如下圖:


左旋轉(zhuǎn)之后

此時(shí),已經(jīng)儼然成為了LL的情況,于是 y 節(jié)點(diǎn)右旋轉(zhuǎn)就解決了啦。

RL:

插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL)。

RL

此時(shí)則需要讓不平衡節(jié)點(diǎn) y 的右孩子 x 進(jìn)行右旋轉(zhuǎn),然后讓不平衡節(jié)點(diǎn) y 的右子樹指向右旋轉(zhuǎn)之后的新的二叉樹的根節(jié)點(diǎn) z。如下圖:


右旋轉(zhuǎn)之后

此時(shí),已經(jīng)儼然成為了RR的情況,于是 y 節(jié)點(diǎn)左旋轉(zhuǎn)就解決了啦。

添加元素:

遞歸操作,遞歸的終止條件:當(dāng)前節(jié)點(diǎn)為NULL,表示到達(dá)最后一個(gè)根節(jié)點(diǎn)NULL,則將新節(jié)點(diǎn)放入該位置。然后比較所給的key與當(dāng)前node節(jié)點(diǎn)的key的大小,如果小于,則新增的節(jié)點(diǎn)放在左子樹,于是往左子樹遞歸;反之,往右子樹遞歸;當(dāng)二者相等時(shí),則更新value值。然后更新node的height,計(jì)算node的平衡因子,如果平衡因子的絕對(duì)值 <= 1,則表示符合AVL特性,直接返回node;否則,就是已經(jīng)破壞了AVL的平衡性,需要根據(jù)不同情況來進(jìn)行左右旋轉(zhuǎn)。

    public void add(K key, V value) {
        root = add(root, key, value);
    }

    // 向 node 為根的二分搜索樹中添加新的元素(key, value)
    // 返回插入新節(jié)點(diǎn)后的二分搜索樹的根
    private Node add(Node node, K key, V value) {
        //遞歸終止條件
        if (node == null) {
            //表示到達(dá)最后一個(gè)根節(jié)點(diǎn)null,則將新節(jié)點(diǎn)放入該位置
            size++;
            return new Node(key, value);
        }
        if (key.compareTo(node.key) < 0){
            //遞歸步驟,往往遞歸終止條件的參數(shù)是和遞歸步驟的參數(shù)對(duì)應(yīng)的
            //將插入新節(jié)點(diǎn)后的二分搜索樹的根掛在當(dāng)前樹上
            node.left = add(node.left, key, value);
        }else if(key.compareTo(node.key) > 0){
            node.right = add(node.right, key, value);
        }else {
            // 更新value值
            node.value = value;
        }
        // 更新 height,左右孩子中大的height + 1
        node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
        // 計(jì)算平衡因子
        int balanceFactory = getBalanceFactory(node);
        if (Math.abs(balanceFactory) <= 1) return node;
        // 不符合平衡二叉樹條件,需要平衡維護(hù)
        if (balanceFactory > 1){
            if (getBalanceFactory(node.left) >= 0){
                // 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL),采用右旋轉(zhuǎn)
                return rightRotate(node);
            }else{
                // 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR),采用先左旋轉(zhuǎn)再右旋轉(zhuǎn)
                node.left = leftRotate(node.left);
                return rightRotate(node);
            }
        }
        if (balanceFactory < -1){
            if (getBalanceFactory(node.right) <= 0){
                // 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR),采用左旋轉(zhuǎn)
                return leftRotate(node);
            }else {
                // 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL),采用右旋轉(zhuǎn)再左旋轉(zhuǎn)
                node.right = rightRotate(node.right);
                return leftRotate(node);
            }
        }
        return node;
    }

刪除元素:

思路:依舊是遞歸執(zhí)行,遞歸的終止條件:1、走到最后沒有找到 key;2、找到key,即node.key.compareTo(key) == 0,此時(shí)也要分情況考慮,當(dāng)左子樹為空,則把右子樹的根節(jié)點(diǎn)作為新二叉樹的根節(jié)點(diǎn),然后將node的右子樹置為NULL,讓GC回收;同理,右子樹為空時(shí),則把左子樹的根節(jié)點(diǎn)作為新二叉樹的根節(jié)點(diǎn),然后將node的左子樹置為NULL;當(dāng)左右子樹都不為空時(shí),則找出右子樹的最小值 successor 用來頂替待刪除節(jié)點(diǎn)的位置(后繼策略)。最后還是需要對(duì)上述操作之后返回的retNode進(jìn)行平衡因子的判斷,如果失去了平衡性,則需要根據(jù)不同情況進(jìn)行左右旋轉(zhuǎn)。

    public V remove(K key) {
        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    // 刪除以node為根的二分搜索樹中的鍵為key的節(jié)點(diǎn)
    // 返回刪除節(jié)點(diǎn)后的新的二分搜索樹
    private Node remove(Node node, K key) {
        // 遞歸終止條件: 1、走到最后沒有找到key ; 2、找到key
        if (node == null) return node;
        Node retNode;
        if (node.key.compareTo(key) == 0){
            if (node.left == null){
                // 待刪除的節(jié)點(diǎn)左子樹為空的情況
                Node rightNode = node.right;
                node.right = null;
                size--;
                retNode = rightNode;
            } else if (node.right == null){
                // 待刪除的節(jié)點(diǎn)右子樹為空的情況
                Node leftNode = node.left;
                node.left = null;
                size--;
                retNode = leftNode;
            }else {
                // 左右子樹都不為空,則找出右子樹的最小值,即采用e 的后繼。然后用這個(gè)節(jié)點(diǎn)頂替待刪除節(jié)點(diǎn)的位置
                Node successor = minimum(node.right);
                //由于removeMin 并未對(duì)刪除元素之后進(jìn)行平衡檢查,所以要么加上,要不不用;
                // 我們這里采用不用,直接使用remove,因?yàn)閟uccessor就是node.right子樹的最小值
//            successor.right = removeMin(node.right);
                successor.right = remove(node.right, successor.key);
                successor.left = node.left;
                node.left = null;
                node.right = null;
                //注意:這里不需要size--,因?yàn)閞emoveMin(node.right) 已經(jīng)操作了size--
                retNode = successor;
            }
        }else if (key.compareTo(node.key) < 0){
            node.left = remove(node.left, key);
            retNode = node;
        }else {
            node.right = remove(node.right, key);
            retNode = node;
        }

        if (retNode == null) return retNode;
        // 更新 height,左右孩子中大的height + 1
        retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
        // 計(jì)算平衡因子
        int balanceFactory = getBalanceFactory(retNode);
        // 不符合平衡二叉樹條件,需要平衡維護(hù)
        if (balanceFactory > 1){
            if (getBalanceFactory(retNode.left) >= 0){
                // 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的左側(cè)(LL),采用右旋轉(zhuǎn)
                return rightRotate(retNode);
            }else{
                // 插入的元素在不平衡節(jié)點(diǎn)的左側(cè)的右側(cè)(LR),采用先左旋轉(zhuǎn)再右旋轉(zhuǎn)
                retNode.left = leftRotate(retNode.left);
                return rightRotate(retNode);
            }
        }
        if (balanceFactory < -1){
            if (getBalanceFactory(retNode.right) <= 0){
                // 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的右側(cè)(RR),采用左旋轉(zhuǎn)
                return leftRotate(retNode);
            }else {
                // 插入的元素在不平衡節(jié)點(diǎn)的右側(cè)的左側(cè)(RL),采用右旋轉(zhuǎn)再左旋轉(zhuǎn)
                retNode.right = rightRotate(retNode.right);
                return leftRotate(retNode);
            }
        }
        return retNode;
    }

小結(jié):

至此,AVL的基本操作已經(jīng)介紹完了,由于它不會(huì)像二分搜索樹那樣退化成鏈表,所以它的添加元素和刪除元素的時(shí)間復(fù)雜度都是O(log n)。

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

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