1. 程式人生 > >AVL樹簡單瞭解

AVL樹簡單瞭解

1.AVL樹定義

在電腦科學中,AVL樹是最早被髮明的自平衡二叉查詢樹。在AVL樹中,任一節點對應的兩棵子樹的最大高度差為1,因此它也被稱為高度平衡樹。

查詢、插入和刪除在平均和最壞情況下的時間複雜度都是 O(logn)。增加和刪除元素的操作則可能需要藉由一次或多次樹旋轉,以實現樹的重新平衡。

AVL樹得名於它的發明者G. M. Adelson-Velsky和Evgenii Landis,他們在1962年的論文《An algorithm for the organization of information》中公開了這一資料結構。

它具有以下兩個性質:

1.任意一個結點的key,比它的左孩子key大,比它的右孩子key小;
2.任意結點的孩子結點之間高度差距最大為1;

2.平衡操作

因為AVL樹是一種平衡樹,所以每次增加或者減少樹中的元素都有可能使這棵樹由平衡變得不平衡,所以我們需要一種機制來檢測這棵樹是否平衡,以及當它不平衡的時候,我們應該通過某些操作使它重新平衡(rebalanced)。

對於一棵樹來說,它的高度(height)定義如下:

從根節點(root)開始到某一個葉子節點(leaf)的最長路徑(path)上結點的個數

根據AVL樹的定義,我們可以為所有的結點定義一個平衡因子(balanced factor):

某個結點的平衡因子等於該節點的左孩子的高度減去右孩子的高度

根據平衡樹的定義,計算得到的平衡因為會出現兩種情況:

  • 如果平衡因子是0, 1, -1 這三個數的話,可以認定該節點是符合平衡樹的定義的;
  • 否則,該結點不平衡,需要重新平衡;

對於一個BST來說,每次插入的元素只可能放在葉子結點上。所以只能影響某個子樹是否平衡,對其他子樹不會有任何的影響。在這種情況下,我們只需要根據搜尋的路徑,從孩子往祖先找,如果有不平衡的結點就可以被找到。如果一直到根結點都沒有發現不平衡結點,則可以認為這次的插入操作沒有造成樹的不平衡。

重平衡

如果發現了某個不平衡的結點,那麼就需要對該結點進行重平衡。實現重平衡的方法,是對該節點的子樹進行旋轉(rotation)。

旋轉之後不會破壞二叉排序樹的平衡。

需要平衡的四種情況

在這裡插入圖片描述

case1:LL,左左。插入或刪除一個節點後,根節點的左子樹的左子樹還有非空子節點,導致"根的左子樹的高度"比"根的右子樹的高度"大2,導致AVL樹失去了平衡。
case2:LR,左右。插入或刪除一個節點後,根節點的左子樹的右子樹還有非空子節點,導致"根的左子樹的高度"比"根的右子樹的高度"大2,導致AVL樹失去了平衡。
case3:RL,右左。插入或刪除一個節點後,根節點的右子樹的左子樹還有非空子節點,導致"根的右子樹的高度"比"根的左子樹的高度"大2,導致AVL樹失去了平衡。
case4: RR,右右。插入或刪除一個節點後,根節點的右子樹的右子樹還有非空子節點,導致"根的右子樹的高度"比"根的左子樹的高度"大2,導致AVL樹失去了平衡。

這四種情況,都可以通過一次或者兩次的旋轉,來使得不平衡的結點變平衡。其中,case1, case4可以通過一次旋轉(singly rotation)重新平衡,case2, case3可以通過兩次旋轉(doubly rotation)重新平衡。

單次旋轉(singly rotatioin)

單次旋轉得到重新平衡的子樹的示意圖如下所示,需要注意的是分清對應的結點。

在這裡插入圖片描述

case1:LL,只需執行一次右旋操作。

private AVLTreeNode<T> rightRotation(AVLTreeNode<T> z) {
	AVLTreeNode<T> y;
	y = z.left;
	z.left = y.right;
	y.right = z;

	z.height = max( height(z.left), height(z.right)) + 1;
	y.height = max( height(y.left), z.height) + 1;

	return y;
}

case4:RR,只需執行一次左旋操作

private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> z) {
	AVLTreeNode<T> y;

	y = z.right;
	z.right = y.left;
	y.left = z;

	z.height = max( height(z.left), height(z.right)) + 1;
	y.height = max( height(y.right), z.height) + 1;

	return y;
}

雙次旋轉(doubly rotatioin)

雙次旋轉顧名思義,就是要進行兩次旋轉來使子樹重新平衡,流程如下圖示:
在這裡插入圖片描述
case2:LR,需要先進行一次左旋轉,然後再進行一次右旋轉;

private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> z) {
	z.left = leftRotation(z.left);
	return rightRotation(z);
}

case3:RL,需要先進行一次右旋轉,然後再進行一次左旋轉;

private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> z) {
	z.right = rightRotation(z.right);
	return leftRotation(z);
}

3.插入元素(Insert)

標準的BST插入元素操作,找到該元素應該被放置的葉子結點,將該元素連線上去;
檢查這次操作是否破壞了樹的平衡,若是,通過旋轉維護平衡特性;

    /*
     * 將結點插入到AVL樹中,並返回根節點
     *
     * 引數說明:
     *     tree AVL樹的根結點
     *     key 插入的結點的鍵值
     * 返回值:
     *     根節點
     */
    private AVLTreeNode<T> insert(AVLTreeNode<T> tree, T key) {
        if (tree == null) {
            // 新建節點
            tree = new AVLTreeNode<T>(key, null, null);
        } else {
            int cmp = key.compareTo(tree.key);

            if (cmp < 0) {    // 應該將key插入到"tree的左子樹"的情況
                tree.left = insert(tree.left, key);
                // 插入節點後,若AVL樹失去平衡,則進行相應的調節。
                if (height(tree.left) - height(tree.right) == 2) {
                    if (key.compareTo(tree.left.key) < 0)
                        tree = rightRotation(tree);
                    else
                        tree = leftRightRotation(tree);
                }
            } else if (cmp > 0) {    // 應該將key插入到"tree的右子樹"的情況
                tree.right = insert(tree.right, key);
                // 插入節點後,若AVL樹失去平衡,則進行相應的調節。
                if (height(tree.right) - height(tree.left) == 2) {
                    if (key.compareTo(tree.right.key) > 0)
                        tree = leftRotation(tree);
                    else
                        tree = rightLeftRotation(tree);
                }
            } else {    // cmp==0
                System.out.println("新增失敗:不允許新增相同的節點!");
            }
        }

        tree.height = max( height(tree.left), height(tree.right)) + 1;

        return tree;
    }

4.刪除元素(Delete)

在一棵樹中,刪除某個元素,邏輯應該是這樣子的:

搜尋給定的key,確定其是否在樹中;
如果不在樹中,返回null;
如果在樹中,執行標準的BST刪除操作,並返回該刪除的結點;
1.如果要刪除的節點正好是葉子節點,直接刪除就 Ok 了;
2.如果要刪除的節點還有子節點,就需要建立父節點和子節點的關係:

  • 如果只有左孩子或者右孩子,直接把這個孩子上移放到要刪除的位置就好了;
  • 如果有兩個孩子,就需要選一個合適的孩子節點作為新的根節點,該節點稱為繼承節點。

然後檢查被刪除結點的所有祖先結點是否平衡,如果不平衡,則執行重平衡操作

	/*
     * 刪除結點(z),返回根節點
     *
     * 引數說明:
     *     tree AVL樹的根結點
     *     z 待刪除的結點
     * 返回值:
     *     根節點
     */
    private AVLTreeNode<T> remove(AVLTreeNode<T> tree, AVLTreeNode<T> z) {
        // 根為空 或者 沒有要刪除的節點,直接返回null。
        if (tree==null || z==null)
            return null;

        int cmp = z.key.compareTo(tree.key);
        if (cmp < 0) {        // 待刪除的節點在"tree的左子樹"中
            tree.left = remove(tree.left, z);
            // 刪除節點後,若AVL樹失去平衡,則進行相應的調節。
            if (height(tree.right) - height(tree.left) == 2) {
                AVLTreeNode<T> r =  tree.right;
                if (height(r.left) > height(r.right))
                    tree = rightLeftRotation(tree);
                else
                    tree = leftRotation(tree);
            }
        } else if (cmp > 0) {// 待刪除的節點在"tree的右子樹"中
            tree.right = remove(tree.right, z);
            // 刪除節點後,若AVL樹失去平衡,則進行相應的調節。
            if (height(tree.left) - height(tree.right) == 2) {
                AVLTreeNode<T> l =  tree.left;
                if (height(l.right) > height(l.left))
                    tree = leftRightRotation(tree);
                else
                    tree = rightRotation(tree);
            }
        } else {// tree是對應要刪除的節點。
            // tree的左右孩子都非空
            if ((tree.left!=null) && (tree.right!=null)) {
                if (height(tree.left) > height(tree.right)) {
                    // 如果tree的左子樹比右子樹高;
                    // 則(01)找出tree的左子樹中的最大節點
                    //   (02)將該最大節點的值賦值給tree。
                    //   (03)刪除該最大節點。
                    // 這類似於用"tree的左子樹中最大節點"做"tree"的替身;
                    // 採用這種方式的好處是:刪除"tree的左子樹中最大節點"之後,AVL樹仍然是平衡的。
                    AVLTreeNode<T> max = maximum(tree.left);
                    tree.key = max.key;
                    tree.left = remove(tree.left, max);
                } else {
                    // 如果tree的左子樹不比右子樹高(即它們相等,或右子樹比左子樹高1)
                    // 則(01)找出tree的右子樹中的最小節點
                    //   (02)將該最小節點的值賦值給tree。
                    //   (03)刪除該最小節點。
                    // 這類似於用"tree的右子樹中最小節點"做"tree"的替身;
                    // 採用這種方式的好處是:刪除"tree的右子樹中最小節點"之後,AVL樹仍然是平衡的。
                    AVLTreeNode<T> min = minimum(tree.right);
                    tree.key = min.key;
                    tree.right = remove(tree.right, min);
                }
            } else {
                AVLTreeNode<T> tmp = tree;
                tree = (tree.left!=null) ? tree.left : tree.right;
                tmp = null;
            }
        }

        if(tree != null)
            tree.height = max(height(tree.left), height(tree.right)) + 1;
        return tree;
    }

5.測試結果

== 依次新增: 3 2 1 4 5 6 7 16 15 14 13 12 11 10 8 9 
== 前序遍歷: 7 4 2 1 3 6 5 13 11 9 8 10 12 15 14 16 
== 中序遍歷: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
== 後序遍歷: 1 3 2 5 6 4 8 10 9 12 11 14 16 15 13 7 
== 高度: 5
== 最小值: 1
== 最大值: 16
== 樹的詳細資訊: 
 7 is root
 4 is  7's   left child
 2 is  4's   left child
 1 is  2's   left child
 3 is  2's  right child
 6 is  4's  right child
 5 is  6's   left child
13 is  7's  right child
11 is 13's   left child
 9 is 11's   left child
 8 is  9's   left child
10 is  9's  right child
12 is 11's  right child
15 is 13's  right child
14 is 15's   left child
16 is 15's  right child

== 刪除根節點: 4
== 高度: 5
== 中序遍歷: 1 2 3 5 6 7 8 9 10 11 12 13 14 15 16 
== 樹的詳細資訊: 
 7 is root
 5 is  7's   left child
 2 is  5's   left child
 1 is  2's   left child
 3 is  2's  right child
 6 is  5's  right child
13 is  7's  right child
11 is 13's   left child
 9 is 11's   left child
 8 is  9's   left child
10 is  9's  right child
12 is 11's  right child
15 is 13's  right child
14 is 15's   left child
16 is 15's  right child

完整程式碼

https://github.com/alinainai/test_11/tree/master/src/avltree

推薦

可以看到 AVL 樹的視覺化,做的非常到位,強烈推薦
https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
紅黑樹
https://blog.csdn.net/u013728021/article/details/84303748

參考:
https://www.cnblogs.com/skywang12345/p/3577479.html#a1
https://zh.wikipedia.org/wiki/AVL樹 有牆
https://www.jianshu.com/p/65c90aa1236d 程式碼有誤
https://blog.csdn.net/juanqinyang/article/details/51418863