Wikipedia - AVL樹node
在計算機科學中,AVL樹是最先被髮明的自平衡二叉查找樹。在AVL樹中,任一節點對應的兩棵子樹的最大高度差爲1,所以它也被稱爲高度平衡樹。查找、插入和刪除在平均和最壞狀況下的時間複雜度都是 {displaystyle O(log {n})} O(log{n})。增長和刪除元素的操做則可能須要藉由一次或屢次樹旋轉,以實現樹的從新平衡。AVL樹得名於它的發明者G. M. Adelson-Velsky和Evgenii Landis,他們在1962年的論文《An algorithm for the organization of information》中公開了這一數據結構。
實現AVL樹的要點爲:每次新增/刪除節點後判斷平衡性而後經過調整使整棵樹從新平衡算法
判斷平衡性:每次新增/刪除節點後,刷新受到影響的節點的高度,便可經過任一節點的左右子樹高度差判斷其平衡性數據結構
調整:經過對部分節點的父子關係的改變使樹從新平衡學習
public class Tree<T extends Comparable<T>> { private static final int MAX_HEIGHT_DIFFERENCE = 1; private Node<T> root; class Node<KT> { KT key; Node<KT> left; Node<KT> right; int height = 1; public Node(KT key, Node<KT> left, Node<KT> right) { this.key = key; this.left = left; this.right = right; } } }
對於任意一次插入所形成的不平衡,均可以簡化爲下述四種範型之一:this
下面四張圖中的數字僅表明節點序號,爲了後文方便展現調整過程
四、五、六、7號節點表明了四棵高度可使不平衡成立的子樹(遵循插入的規則)spa
總結獲得判斷範型的方法爲:不平衡的節點(節點1)通往高度最大的子樹的葉子節點時所途經的前兩個節點(節點二、節點3)的方向設計
5號節點
做爲1號節點
的左孩子1號節點
做爲2號節點
的右孩子例子(例子中的數字表明節點的值):3d
插入節點5
後形成節點9
不平衡,其範型爲LL型
,按照固定步驟調整後全局從新達到平衡code
6號節點
做爲2號節點
的右孩子7號節點
做爲1號節點
的左孩子2號節點
做爲3號節點
的左孩子1號節點
做爲3號節點
的右孩子例子(例子中的數字表明節點的值):orm
插入節點8.5
後形成節點9
不平衡,其範型爲LR型
,按照固定步驟調整後全局從新達到平衡
5號節點
做爲1號節點
的右孩子1號節點
做爲2號節點
的左孩子例子(例子中的數字表明節點的值):
插入節點10.5
後形成節點7
不平衡,其範型爲RR型
,按照固定步驟調整後全局從新達到平衡
7號節點
做爲2號節點
的左孩子6號節點
做爲1號節點
的右孩子2號節點
做爲3號節點
的右孩子1號節點
做爲3號節點
的左孩子例子(例子中的數字表明節點的值):
插入節點7.5
後形成節點7
不平衡,其範型爲RL型
,按照固定步驟調整後全局從新達到平衡
public void insert(T key) { if (key == null) { throw new NullPointerException(); } root = insert(root, key); } private Node<T> insert(Node<T> node, T key) { if (node == null) { return new Node<>(key, null, null); } int cmp = key.compareTo(node.key); if (cmp == 0) { return node; } if (cmp < 0) { node.left = insert(node.left, key); } else { node.right = insert(node.right, key); } if (Math.abs(height(node.left) - height(node.right)) > MAX_HEIGHT_DIFFERENCE) { node = balance(node); } refreshHeight(node); return node; } private int height(Node<T> node) { if (node == null) { return 0; } return node.height; } private void refreshHeight(Node<T> node) { node.height = Math.max(height(node.left), height(node.right)) + 1; } /** * 此方法中的node, node1, node2分別表明上文範型中的一、二、3號節點 */ private Node<T> balance(Node<T> node) { Node<T> node1, node2; // ll if (height(node.left) > height(node.right) && height(node.left.left) > height(node.left.right)) { node1 = node.left; node.left = node1.right; node1.right = node; refreshHeight(node); return node1; } // lr if (height(node.left) > height(node.right) && height(node.left.right) > height(node.left.left)) { node1 = node.left; node2 = node.left.right; node.left = node2.right; node1.right = node2.left; node2.left = node1; node2.right = node; refreshHeight(node); refreshHeight(node1); return node2; } // rr if (height(node.right) > height(node.left) && height(node.right.right) > height(node.right.left)) { node1 = node.right; node.right = node1.left; node1.left = node; refreshHeight(node); return node1; } // rl if (height(node.right) > height(node.left) && height(node.right.left) > height(node.right.right)) { node1 = node.right; node2 = node.right.left; node.right = node2.left; node1.left = node2.right; node2.left = node; node2.right = node1; refreshHeight(node); refreshHeight(node1); return node2; } return node; }
由插入節點致使的局部不平衡均會符合上述四種範型之一,只須要按照固定的方式調整相關節點的父子關係便可使樹恢復平衡
關於調整,不少博客或者書籍中將這種調整父子關係的過程稱爲旋轉,這個就見仁見智了,我的以爲這種描述並不容易理解,故本文統一稱爲調整
對於刪除節點這個操做來講,有兩個要點:被刪除節點的空缺應該如何填補以及刪除後如何使樹恢復平衡
例子:
節點9
,則應當使用左子樹中的最大值節點8
來填補空缺節點13
,則應當使用右子樹中的最小值節點14
來填補空缺節點2
,則使用左子樹中的最大值節點1.5
或者右子樹中的最小值節點2.5
來填補空缺都可按照上述方式來填補空缺,能夠儘量保證刪除後整棵樹仍然保持平衡
如圖,葉子節點12
爲被刪除節點,刪除後不須要填補空缺,可是此時節點13
產生了不平衡
不過節點13
的不平衡知足上文所說的不平衡範型中的RR型
,所以只須要對節點13
作對應的調整便可,如圖:
此時節點13
所在的子樹通過調整從新達到局部平衡
可是咱們緊接着發現,節點11
出現了不平衡,其左子樹高度爲4,右子樹高度爲2
若是此時按照插入狀況下的不平衡範型判斷方法去判斷節點11
的不平衡狀況屬於哪一種範型,會發現沒法知足四種範型的任一狀況
由刪除節點致使的不平衡,除了會出現插入中所說的四種範型以外,還會出現兩種狀況,如圖:
整棵樹初始狀態爲平衡狀態,此時假設刪除節點13
或節點14
,均會致使節點11
產生不平衡(左子樹高度3,右子樹高度1)
可是若是仍然按照插入時的方法來判斷不平衡,則會發現,節點4
的左右子樹高度一致,即在知足了L
後,後續沒法判斷這種狀況屬於哪一種範型
對於R
方向也是同樣
本文稱它們爲L型
和R型
不過這兩種狀況的處理也很簡單,實際上當出現這種狀況時,使用LL型
或LR型
的調整方法都可以達到使樹從新平衡的目的
如圖:
兩種調整方式都可使樹從新平衡,對於R型
也是同樣,這裏再也不贅述
public void remove(T key) { if (key == null) { throw new NullPointerException(); } root = remove(root, key); } private Node<T> remove(Node<T> node, T key) { if (node == null) { return null; } int cmp = key.compareTo(node.key); if (cmp < 0) { node.left = remove(node.left, key); } if (cmp > 0){ node.right = remove(node.right, key); } if (cmp == 0) { if (node.left == null || node.right == null) { return node.left == null ? node.right : node.left; } var successorKey = successorOf(node).key; node = remove(node, successorKey); node.key = successorKey; } if (Math.abs(height(node.left) - height(node.right)) > MAX_HEIGHT_DIFFERENCE) { node = balance(node); } refreshHeight(node); return node; } /** * 尋找被刪除節點的繼承者 */ private Node<T> successorOf(Node<T> node) { if (node == null) { throw new NullPointerException(); } if (node.left == null || node.right == null) { return node.left == null ? node.right : node.left; } return height(node.left) > height(node.right) ? findMax(node.left, node.left.right, node.left.right == null) : findMin(node.right, node.right.left, node.right.left == null); } private Node<T> findMax(Node<T> node, Node<T> right, boolean rightIsNull) { if (rightIsNull) { return node; } return findMax((node = right), node.right, node.right == null); } private Node<T> findMin(Node<T> node, Node<T> left, boolean leftIsNull) { if (leftIsNull) { return node; } return findMin((node = left), node.left, node.left == null); }
其中用到的private Node<T> balance(Node<T> node)
方法修改成:
private Node<T> balance(Node<T> node) { Node<T> node1, node2; // ll & l if (height(node.left) > height(node.right) && height(node.left.left) >= height(node.left.right)) { node1 = node.left; node.left = node1.right; node1.right = node; refreshHeight(node); return node1; } // lr if (height(node.left) > height(node.right) && height(node.left.right) > height(node.left.left)) { node1 = node.left; node2 = node.left.right; node.left = node2.right; node1.right = node2.left; node2.left = node1; node2.right = node; refreshHeight(node); refreshHeight(node1); return node2; } // rr & r if (height(node.right) > height(node.left) && height(node.right.right) >= height(node.right.left)) { node1 = node.right; node.right = node1.left; node1.left = node; refreshHeight(node); return node1; } // rl if (height(node.right) > height(node.left) && height(node.right.left) > height(node.right.right)) { node1 = node.right; node2 = node.right.left; node.right = node2.left; node1.left = node2.right; node2.left = node; node2.right = node1; refreshHeight(node); refreshHeight(node1); return node2; } return node; }
也就是將L型
狀況包含進了LL型
,R型
的狀況包含進了RR型
,由於這兩種範式的調整要比對應的LR型
/RL型
的操做數少
儘管刪除節點時會出現特殊的狀況,可是仍然能夠經過簡單的調整使樹始終保持平衡
/** * AVL-Tree * * @author Shinobu * @since 2019/5/7 */ public class Tree<T extends Comparable<T>> { private static final int MAX_HEIGHT_DIFFERENCE = 1; private Node<T> root; class Node<KT> { KT key; Node<KT> left; Node<KT> right; int height = 1; public Node(KT key, Node<KT> left, Node<KT> right) { this.key = key; this.left = left; this.right = right; } } public Tree(T... keys) { if (keys == null || keys.length < 1) { throw new NullPointerException(); } root = new Node<>(keys[0], null, null); for (int i = 1; i < keys.length && keys[i] != null; i++) { root = insert(root, keys[i]); } } public T find(T key) { if (key == null || root == null) { return null; } return find(root, key, key.compareTo(root.key)); } private T find(Node<T> node, T key, int cmp) { if (node == null) { return null; } if (cmp == 0) { return node.key; } return find( (node = cmp > 0 ? node.right : node.left), key, node == null ? 0 : key.compareTo(node.key)); } public void insert(T key) { if (key == null) { throw new NullPointerException(); } root = insert(root, key); } private Node<T> insert(Node<T> node, T key) { if (node == null) { return new Node<>(key, null, null); } int cmp = key.compareTo(node.key); if (cmp == 0) { return node; } if (cmp < 0) { node.left = insert(node.left, key); } else { node.right = insert(node.right, key); } if (Math.abs(height(node.left) - height(node.right)) > MAX_HEIGHT_DIFFERENCE) { node = balance(node); } refreshHeight(node); return node; } private int height(Node<T> node) { if (node == null) { return 0; } return node.height; } private void refreshHeight(Node<T> node) { node.height = Math.max(height(node.left), height(node.right)) + 1; } private Node<T> balance(Node<T> node) { Node<T> node1, node2; // ll & l if (height(node.left) > height(node.right) && height(node.left.left) >= height(node.left.right)) { node1 = node.left; node.left = node1.right; node1.right = node; refreshHeight(node); return node1; } // lr if (height(node.left) > height(node.right) && height(node.left.right) > height(node.left.left)) { node1 = node.left; node2 = node.left.right; node.left = node2.right; node1.right = node2.left; node2.left = node1; node2.right = node; refreshHeight(node); refreshHeight(node1); return node2; } // rr & r if (height(node.right) > height(node.left) && height(node.right.right) >= height(node.right.left)) { node1 = node.right; node.right = node1.left; node1.left = node; refreshHeight(node); return node1; } // rl if (height(node.right) > height(node.left) && height(node.right.left) > height(node.right.right)) { node1 = node.right; node2 = node.right.left; node.right = node2.left; node1.left = node2.right; node2.left = node; node2.right = node1; refreshHeight(node); refreshHeight(node1); return node2; } return node; } public void remove(T key) { if (key == null) { throw new NullPointerException(); } root = remove(root, key); } private Node<T> remove(Node<T> node, T key) { if (node == null) { return null; } int cmp = key.compareTo(node.key); if (cmp < 0) { node.left = remove(node.left, key); } if (cmp > 0){ node.right = remove(node.right, key); } if (cmp == 0) { if (node.left == null || node.right == null) { return node.left == null ? node.right : node.left; } var successorKey = successorOf(node).key; node = remove(node, successorKey); node.key = successorKey; } if (Math.abs(height(node.left) - height(node.right)) > MAX_HEIGHT_DIFFERENCE) { node = balance(node); } refreshHeight(node); return node; } private Node<T> successorOf(Node<T> node) { if (node == null) { throw new NullPointerException(); } if (node.left == null || node.right == null) { return node.left == null ? node.right : node.left; } return height(node.left) > height(node.right) ? findMax(node.left, node.left.right, node.left.right == null) : findMin(node.right, node.right.left, node.right.left == null); } private Node<T> findMax(Node<T> node, Node<T> right, boolean rightIsNull) { if (rightIsNull) { return node; } return findMax((node = right), node.right, node.right == null); } private Node<T> findMin(Node<T> node, Node<T> left, boolean leftIsNull) { if (leftIsNull) { return node; } return findMin((node = left), node.left, node.left == null); } }
AVL樹的實現,在瞭解了不平衡的六種狀況,以及對應的處理方式後,仍是比較簡單且邏輯清晰的
經過對AVL樹的學習,能夠發現它是一種「對不平衡很是敏感」的結構——能夠容忍的高度差僅爲1。這雖然可讓樹儘量的平衡,使查找效率儘量高,但也付出了相應的代價: 調整平衡。
它的插入元素引起的調整的最壞時間複雜度爲O(1),可是刪除引起的最壞時間複雜度爲O(logN),這正是AVL樹的弊端所在。
因此後來的2-3樹、2-3-4樹、紅黑樹都嘗試對這種弊端進行了改進,改進的思路能夠大概理解爲兩種:
以上,但願本文對讀到的朋友能有所幫助
文章若是有謬誤或疏漏,還請務必指正,感謝萬分