淺析樹結構(二)AVL平衡二叉樹(AVL樹原理及代碼實現)

平衡二叉樹

平衡二叉樹定義


平衡二叉樹有兩種形式:

  1. 是一棵空樹
  2. 是一個左右兩個子樹的高度差的絕對值不超過1,並且左右子樹都是平衡二叉樹的樹


平衡二叉樹的常用實現方法有AVL樹,紅黑樹(紅黑樹並不是嚴格意義上的平衡,並不一定滿足任意節點的左右兩棵子樹高度差不超過1這個條件),替罪羊樹,Treap,伸展樹等.
在這裏我們先談AVL樹,紅黑樹會在之後的文章再提出.

AVL樹

AVL是最先發明的自平衡二叉查找樹算法。在AVL中任何節點的兩個兒子子樹的高度最大差別爲1,所以它也被稱爲高度平衡樹,n個結點的AVL樹最大深度約1.44log2n。查找、插入和刪除在平均和最壞情況下都是O(log n)。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。

基本操作(操作比較簡單不再贅述)

主要包括:

  1. 內部節點類Node.class
  2. 樹的根節點root
  3. 查看樹的大小方法size()
  4. 更新樹的大小方法updateSize()
  5. 獲取節點高度方法getHeight(),葉節點高度默認爲1(計算平衡因子需要使用height)
  6. 獲取節點平衡因子方法getBalanceFactor(),平衡因子=左節點height-右節點height,若平衡因子的絕對值超過1說明該節點不平衡,即該樹不平衡.

平衡因子是判斷樹是否平衡的依據,之後的平衡操作都要依據平衡因子來決定是否進行旋轉操作和進行怎樣的旋轉操作.

import java.util.ArrayList;
import java.util.List;

public class AVLTree <Key extends Comparable<? super Key>,Value> {
    /** 根節點 */
    private Node root;

    /** 內部節點類 */
    private class Node{
        private Key key;//鍵
        private Value value;//值
        private Node left;//左子節點
        private Node right;//右子節點
        private int N;//以該節點爲根節點的子樹中的節點總數
        private int height=1;//節點的高度
        Node(Key key,Value value,int N){
            this.key=key;
            this.value=value;
            this.N=N;
        }
    }
    /**
     * 整棵樹的節點數量
     * @return
     */
    public int size(){
        return size(root);
    }

    /**
     * 返回以node節點爲根的子樹的大小(節點數量)
     * @param node
     * @return
     */
    private int size(Node node){
        if(node==null){
            return 0;
        }
        return node.N;
    }

    /**
     * 更新節點的size
     * @param node
     * @return
     */
    private int updateSize(Node node){
        return size(node.left)+size(node.right)+1;
    }

    /**
     * 更新節點的高度height
     * @param node
     * @return
     */
    private int updateHeight(Node node){
        return Math.max(getHeight(node.left),getHeight(node.right))+1;
    }
    private int getHeight(Node node){
        if(node==null){
            return 0;
        }
        return node.height;
    }
    /**
     * 獲取節點的平衡因子
     * @param node
     * @return
     */
    private int getBalanceFactor(Node node){
        return getHeight(node.left)-getHeight(node.right);
    }

查找操作(操作比較簡單不再贅述)

主要包括:

  1. 根據key來查找對應value的方法get()
  2. 查找樹中最小節點的方法min()
  3. 查找樹中最大節點的方法max()

    /**
     * 查找樹中key對應的value值
     * @param key 要查找的key值
     * @return key對應的value值,不存在則返回null
     */
    public Value get(Key key){
        return get(root,key);
    }

    /**
     * 查找以node節點爲根節點的子樹中key對應的value值
     * @param node 要作爲根節點的節點
     * @param key 要查找的key值
     * @return key對應的value值,不存在則返回null
     */
    private Value get(Node node,Key key){
        if(node==null){
            throw new RuntimeException("Cannot find key "+key);
        }
        int compareResult = key.compareTo(node.key);
        if(compareResult<0){
            return get(node.left,key);
        }else if(compareResult>0){
            return get(node.right,key);
        }else{
            return node.value;
        }
    }

    /**
     * 查找樹中最小的key
     * @return 最小key值
     */
    public Key min(){
        return min(root).key;
    }

    /**
     * 查找以node爲根節點的子樹中最小的key
     * @param node 子樹的跟節點
     * @return 最小的key對應的node
     */
    private Node min(Node node){
        //如果樹爲空,則直接返回null
        if(node==null){
            return null;
        }
        //如果節點爲葉子節點,則返回該節點
        if(node.left==null){
            return node;
        }
        //遞歸查找最左邊的節點,也就是最小的節點
        return min(node.left);
    }

    /**
     * 查找樹中最大的key
     * @return 最大key值
     */
    public Key max(){
        return max(root).key;
    }

    /**
     * 查找以node爲根節點的子樹中最大的key
     * @param node 子樹的根節點
     * @return 最大的key對應的node
     */
    private Node max(Node node){
        if(node==null){
            return null;
        }
        if(node.right==null){
            return node;
        }
        return max(node.right);
    }

插入操作

插入操作與二叉查找樹(BST)基本一致 , 唯一不同是加了一步node=balance(node);平衡操作,因爲插入節點有可能會破壞樹的平衡狀態,因此需要進行旋轉平衡操作.具體如何進行旋轉平衡請查看後面的private Node balance(Node node){...}方法.

    /**
     * 插入節點
     * @param key 要插入的節點的key
     * @param value 要插入的節點的value
     */
    public void put(Key key,Value value){
        root=put(root,key,value);
    }

    private Node put(Node node,Key key, Value value){
        //node==null則表示已經找到要插入新節點的地方
        if(node==null){
            return new Node(key,value,1);
        }
        int compareResult = key.compareTo(node.key);
        if(compareResult<0){
            node.left=put(node.left,key,value);
        }else if(compareResult>0){
            node.right=put(node.right,key,value);
        }else{
            node.value=value;//如果存在相同的key,則更新其value值
        }
        node.N=updateSize(node);//新增節點需要更新N值
        node.height=updateHeight(node);//更新height值

        node=balance(node);//平衡操作
        return node;
    }

刪除操作

刪除操作與二叉查找樹(BST)也是基本一致,不再贅述,唯一不同的是刪除操作同樣有可能破壞樹的平衡性,因此在刪除節點後如果樹的平衡性被破壞,需要對樹進行平衡操作. 平衡操作同樣請查看後面的private Node balance(Node node){...}方法.

 	/**
     * 刪除key對應的節點
     * @param key
     */
    public void delete(Key key){
        root=delete(root,key);
    }
    private Node delete(Node node,Key key){
        //沒有查找到
        if(node==null){
            return null;
        }
        int compareResult = key.compareTo(node.key);
        if(compareResult<0){
            //要刪除的節點位於左子樹
            node.left=delete(node.left,key);
        }else if(compareResult>0){
            //要刪除的節點位於右子樹
            node.right=delete(node.right,key);
        }else{
            /**
             *查找到要刪除的節點,分成三種情況
             * 1.要刪除的節點有左右子樹
             * 2.要刪除的節點有且僅有左子樹或者右子樹
             * 3.要刪除的節點爲葉子節點,即沒有子樹
             */
            if(node.left!=null&&node.right!=null){
                Node minOfRightTree = min(node.right);//node節點中右子樹中最小的節點
                node.key=minOfRightTree.key;//替換掉要刪除節點的key
                node.value=minOfRightTree.value;//替換掉要刪除節點的value
                node.right=delete(node.right,minOfRightTree.key);
            }else {
                //情況2和情況3均使用子節點替換掉要刪除的節點即可.
                node=node.left!=null?node.left:node.right;
            }
        }
        //如果刪除的不是葉子節點
        if(node!=null){
            node.N=updateSize(node);
            node.height=updateHeight(node);
            node=balance(node);//平衡操作
        }
        return node;
    }

平衡操作

破壞樹的平衡性的四種情形分別是LL, RR, LR, RL
先來看LL和RR這兩種情況(因爲LR和RL也是轉換成這兩種情況然後再進行處理的)

1. LL情形解決方法

LL需要進行右旋轉(即順時針旋轉),來降低樹的高度.

2. RR情形解決方法

RR需要進行左旋轉(即逆時針旋轉),來降低樹的高度.

3. LR情形的解決方法

RR和LL都只需要一次旋轉就可以實現平衡.而LR則需要先進行左旋轉來轉化爲LL情形,然後再如LL情形一樣進行右旋轉,總共兩次旋轉來實現平衡.

轉化爲LL的情形:

4. RL情形的解決方法

RR和LL都只需要一次旋轉就可以實現平衡.而RL則需要先進行右旋轉來轉化爲RR情形,然後再如RR情形一樣進行左旋轉,總共兩次旋轉來實現平衡.

轉化爲RR的情形:

private Node balance(Node node)方法

    private Node balance(Node node){
        if(Math.abs(getBalanceFactor(node))>1){
            //LL情況,對根節點使用右旋轉,如果getBalanceFactor(node.left)大於等於0修改爲大於0,則等於的情況需要的LR情況下處理,增加了不必要的左旋轉操作。RR情況同理
            if((getBalanceFactor(node)>1)&&(getBalanceFactor(node.left)>=0)){
                node=rightRotate(node);
            }
            //RR情況,對根節點使用左旋轉
            else if((getBalanceFactor(node)<-1)&&(getBalanceFactor(node.right)<=0)){
                node=leftRotate(node);
            }
            //LR情況,先對根節點的左子樹使用左旋轉,後對根節點使用右旋轉
            else if((getBalanceFactor(node)>1)&&(getBalanceFactor(node.left)<0)){
                node.left=leftRotate(node.left);
                node=rightRotate(node);
            }
            //RL情況,先是對根節點的右子樹使用右旋轉,後對根節點使用左旋轉
            else{
                node.right=rightRotate(node.right);
                node=leftRotate(node);
            }
        }
        return node;
    }


    /**
     * 右旋轉
     * @param x
     * @return
     */
    private Node rightRotate(Node x){
        Node y=x.left;
        Node T3=y.right;
        x.left=T3;
        y.right=x;
        //更新height
        x.height=updateHeight(x);
        y.height=updateHeight(y);
        x.N=updateSize(x);
        y.N=updateSize(y);
        return y;
    }

    /**
     * 左旋轉
     * @param x
     * @return
     */
    private Node leftRotate(Node x){
        Node y=x.right;
        Node T3=y.left;
        x.right=T3;
        y.left=x;
        //更新height
        x.height=updateHeight(x);
        y.height=updateHeight(y);
        x.N=updateSize(x);
        y.N=updateSize(y);
        return y;
    }

中序遍歷操作

    public List<Key> keys(){
        ArrayList<Key> list = new ArrayList<>();
        keys(root,list);
        return list;
    }
    private void keys(Node node, ArrayList<Key> list){
        if(node==null){
            return;
        }
        keys(node.left,list);
        list.add(node.key);
        keys(node.right,list);
    }
}

淺析樹結構(一)二叉查找樹(BST樹代碼實現)
淺析樹結構(二)AVL平衡二叉樹(AVL樹原理及代碼實現)
淺析樹結構(三)紅黑樹

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