本文從屬於筆者的數據結構與算法系列文章。java
二叉查找樹能夠表示動態的數據集合,對於給定的數據集合,在創建一顆二叉查找樹時,二叉查找樹的結構形態與關鍵字的插入順序有關。若是所有或者部分地按照關鍵字的遞增或者遞減順序插入二叉查找樹的結點,則所創建的二叉查找樹所有或者在局部造成退化的單分支結構。在最壞的狀況下,二叉查找樹可能徹底偏斜,高度爲n
,其平均與最壞的狀況下查找時間都是O(n);
而最好的狀況下,二叉查找樹的結點儘量靠近根結點,其平均與最好狀況的查找時間都是O(logn)
。所以,咱們但願最理想的狀態下是使二叉查找樹始終處於良好的結構形態。git
1962年,Adelson-Velsikii和Landis提出了一種結點在高度上相對平衡的二叉查找樹,又稱爲AVL樹。其平均和最壞狀況下的查找時間都是O(logn)
。同時,插入和刪除的時間複雜性也會保持O(logn)
,且在插入和刪除以後,在高度上仍然保持平衡。AVL樹又稱爲平衡二叉樹,即Balanced Binary Tree或者Height-Balanced Tree,它或者是一棵空二叉樹,或者是具備以下性質的二叉查找樹:其左子樹和右子樹都是高度平衡的二叉樹,且左子樹和右子樹的高度之差的絕對值不超過1。若是將二叉樹上結點的平衡因子BF(Balanced Factor)定義爲該結點的左子樹與右子樹的高度之差,根據AVL樹的定義,AVL樹中的任意結點的平衡因子只多是-1(右子樹高於左子樹)、0或者1(左子樹高於右子樹),在某些圖中也會表示爲絕對高度差,即0,1,2這種形式,請注意理解。github
BalanceFactor = height(left-sutree) − height(right-sutree)
AVL樹中的結點的數據結構能夠表示爲:算法
package wx.algorithm.search.avl; /** * Created by apple on 16/7/30. */ public class AVLNode { //節點的值 public int key; //節點的平衡度 public int balance; //分別指向節點的左孩子、右孩子與父節點 public AVLNode left, right, parent; /** * @function 默認構造函數 * @param k * @param p */ AVLNode(int k, AVLNode p) { key = k; parent = p; } }
AVL樹的調整過程很相似於數學概括法,每次在插入新節點以後都會找到離新插入節點最近的非平衡葉節點,而後對其進行旋轉操做以使得樹中的每一個節點都處於平衡狀態。數據結構
當新插入的結點爲右子樹的右子結點時,咱們須要進行左旋操做來保證此部分子樹繼續處於平衡狀態。app
咱們應該找到離新插入的結點最近的一個非平衡結點,來以其爲軸進行旋轉,下面看一個比較複雜的狀況:函數
/** * @param a * @return * @function 左旋操做 */ private AVLNode rotateLeft(AVLNode a) { //指向當前節點的右孩子 AVLNode b = a.right; //將當前節點的右孩子掛載到當前節點的父節點 b.parent = a.parent; //將本來節點的右孩子掛載到新節點的左孩子 a.right = b.left; if (a.right != null) a.right.parent = a; //將本來節點掛載到新節點的左孩子上 b.left = a; //將本來節點的父節點設置爲新節點 a.parent = b; //若是當前節點的父節點不爲空 if (b.parent != null) { if (b.parent.right == a) { b.parent.right = b; } else { b.parent.left = b; } } //從新計算每一個節點的平衡度 setBalance(a, b); return b; }
當新插入的結點爲左子樹的左子結點時,咱們須要進行右旋操做來保證此部分子樹繼續處於平衡狀態。spa
下面看一個比較複雜的狀況:3d
private AVLNode rotateRight(AVLNode a) { AVLNode b = a.left; b.parent = a.parent; a.left = b.right; if (a.left != null) a.left.parent = a; b.right = a; a.parent = b; if (b.parent != null) { if (b.parent.right == a) { b.parent.right = b; } else { b.parent.left = b; } } setBalance(a, b); return b; }
在某些狀況下咱們須要進行兩次旋轉操做,譬如在以下的狀況下,某個結點被插入到了左子樹的右子結點:code
咱們首先要以A爲軸進行左旋操做:
而後須要以C爲軸進行右旋操做:
最終獲得的又是一棵平衡樹:
private AVLNode rotateLeftThenRight(AVLNode n) { n.left = rotateLeft(n.left); return rotateRight(n); }
private AVLNode rotateRightThenLeft(AVLNode n) { n.right = rotateRight(n.right); return rotateLeft(n); }
Java實現的核心代碼地址爲:AVLTree
package wx.algorithm.search.avl; /** * Created by apple on 16/7/30. */ public class AVLTree { //指向當前AVL樹的根節點 private AVLNode root; /** * @param key * @return * @function 插入函數 */ public boolean insert(int key) { //若是當前根節點爲空,則直接建立新節點 if (root == null) root = new AVLNode(key, null); else { //設置新的臨時節點 AVLNode n = root; //指向當前的父節點 AVLNode parent; //循環直至找到合適的插入位置 while (true) { //若是查找到了相同值的節點 if (n.key == key) //則直接報錯 return false; //將當前父節點指向當前節點 parent = n; //判斷是移動到左節點仍是右節點 boolean goLeft = n.key > key; n = goLeft ? n.left : n.right; //若是左孩子或者右孩子爲空 if (n == null) { if (goLeft) { //將節點掛載到左孩子上 parent.left = new AVLNode(key, parent); } else { //不然掛載到右孩子上 parent.right = new AVLNode(key, parent); } //重平衡該樹 rebalance(parent); break; } //若是不爲空,則以n爲當前節點進行查找 } } return true; } /** * @param delKey * @function 根據關鍵值刪除某個元素, 須要對樹進行再平衡 */ public void delete(int delKey) { if (root == null) return; AVLNode n = root; AVLNode parent = root; AVLNode delAVLNode = null; AVLNode child = root; while (child != null) { parent = n; n = child; child = delKey >= n.key ? n.right : n.left; if (delKey == n.key) delAVLNode = n; } if (delAVLNode != null) { delAVLNode.key = n.key; child = n.left != null ? n.left : n.right; if (root.key == delKey) { root = child; } else { if (parent.left == n) { parent.left = child; } else { parent.right = child; } rebalance(parent); } } } /** * @function 打印節點的平衡度 */ public void printBalance() { printBalance(root); } /** * @param n * @function 重平衡該樹 */ private void rebalance(AVLNode n) { //爲每一個節點設置相對高度 setBalance(n); //若是左子樹高於右子樹 if (n.balance == -2) { //若是掛載的是左子樹的左孩子 if (height(n.left.left) >= height(n.left.right)) //進行右旋操做 n = rotateRight(n); else //若是掛載的是左子樹的右孩子,則先左旋後右旋 n = rotateLeftThenRight(n); } //若是左子樹高於右子樹 else if (n.balance == 2) { //若是掛載的是右子樹的右孩子 if (height(n.right.right) >= height(n.right.left)) //進行左旋操做 n = rotateLeft(n); else //不然進行先右旋後左旋 n = rotateRightThenLeft(n); } if (n.parent != null) { //若是當前節點的父節點不爲空,則平衡其父節點 rebalance(n.parent); } else { root = n; } } /** * @param a * @return * @function 左旋操做 */ private AVLNode rotateLeft(AVLNode a) { //指向當前節點的右孩子 AVLNode b = a.right; //將當前節點的右孩子掛載到當前節點的父節點 b.parent = a.parent; //將本來節點的右孩子掛載到新節點的左孩子 a.right = b.left; if (a.right != null) a.right.parent = a; //將本來節點掛載到新節點的左孩子上 b.left = a; //將本來節點的父節點設置爲新節點 a.parent = b; //若是當前節點的父節點不爲空 if (b.parent != null) { if (b.parent.right == a) { b.parent.right = b; } else { b.parent.left = b; } } //從新計算每一個節點的平衡度 setBalance(a, b); return b; } private AVLNode rotateRight(AVLNode a) { AVLNode b = a.left; b.parent = a.parent; a.left = b.right; if (a.left != null) a.left.parent = a; b.right = a; a.parent = b; if (b.parent != null) { if (b.parent.right == a) { b.parent.right = b; } else { b.parent.left = b; } } setBalance(a, b); return b; } private AVLNode rotateLeftThenRight(AVLNode n) { n.left = rotateLeft(n.left); return rotateRight(n); } private AVLNode rotateRightThenLeft(AVLNode n) { n.right = rotateRight(n.right); return rotateLeft(n); } /** * @param n * @return * @function 計算某個節點的高度 */ private int height(AVLNode n) { if (n == null) return -1; return 1 + Math.max(height(n.left), height(n.right)); } /** * @param AVLNodes * @function 重設置每一個節點的平衡度 */ private void setBalance(AVLNode... AVLNodes) { for (AVLNode n : AVLNodes) n.balance = height(n.right) - height(n.left); } private void printBalance(AVLNode n) { if (n != null) { printBalance(n.left); System.out.printf("%s ", n.balance); printBalance(n.right); } } }