普通二叉搜索樹可能出現一條分支有多層,而其餘分支卻只有幾層的狀況,如圖1所示,這會致使添加、移除和搜索樹具備性能問題。所以提出了自平衡二叉樹的概念,AVL樹(阿德爾森-維爾斯和蘭迪斯樹)是自平衡二叉樹的一種,AVL樹的任一子節點的左右兩側子樹的高度之差不超過1,因此它也被稱爲高度平衡樹。node
要將不平衡的二叉搜索樹轉換爲平衡的AVL樹須要對樹進行一次或屢次旋轉,旋轉方式分爲左單旋、右單旋、左-右雙旋、右-左雙旋。segmentfault
對某一節點B(圖2)作左單旋,處理過程至關於,斷開B與父節點A的鏈接,將B的右子節點D與A鏈接,將B做爲D的左子節點,將D的左子節點E做爲B的右子節點。以圖1的二叉樹爲例,對鍵值爲15的節點作左單旋,首先斷開15與11的鏈接,再將20與11鏈接,將15做爲20的左子節點,最後將18做爲15的右子節點;能夠想象爲以15爲中心作了必定的逆時針旋轉。結果如圖3。函數
再看圖2,根據搜索二叉樹的性質,確定有D>B>A,E>B,所以旋轉事後,可以保證 右子節點 > 父節點 > 左子節點,不會破壞樹的結構。
能夠看到,一次左單旋將右側子樹的高度減少了1,而左側子樹的高度增長了1。實現代碼以下:性能
function roateLeft(AvlNode) { var node = AvlNode.right; // 保存右子節點 AvlNode.right = node.left; // node的左子節點鏈接到AvlNode成爲其右子節點 node.left = AvlNode; // AvlNode鏈接到node成爲其左子節點 return node; // 返回node,鏈接到AvlNode最初的父節點 }
右單旋與左單選相似,以某一節點B(圖4)作右單旋,首先斷開B與其父節點A的鏈接,將B的左子節點C與A鏈接,將C的右子節點F做爲B的左子節點。一樣的,由於有C>A,B>F>C,所以旋轉事後,不會破壞樹的結構。能夠看到,一次右單旋使節點的左側子樹高度減少了1,而右側子樹的高度增長了1。spa
實現代碼以下:3d
function roateRight(AvlNode) { var node = AvlNode.left; // 保存左子節點 AvlNode.left = node.right; // 將node的右子節點鏈接到AvlNode成爲其左子節點 node.right = AvlNode; // AvlNode鏈接到node,成爲其右子節點 return node; // 返回node鏈接到AvlNode最初的父節點 }
左單旋、右單旋在某些狀況下是不能達到平衡樹的目的的。如圖4,對B進行右單旋,須要左子樹C的右子樹F的高度小於等於左子樹E的高度,不然不能達到平衡的效果,只是把不平衡性從左邊轉移到了右邊。圖5演示了這種狀況。一樣的,左單旋也有這個問題。code
所以爲了達到目的,須要先對旋轉節點的左子節點作左單旋,再對旋轉節點作右單旋。如圖6所示,先對節點B的左子節點C作左單旋,能夠看到,這個操做,至關於將節點C的不平衡性從右側轉移到了左側,從而知足了上述右單旋的條件;最後再對B節點作右單旋操做,最終達到了平衡的目的。blog
實現代碼以下:ip
function roateLeftRight(AvlNode) { AvlNode.right = roateLeft(AvlNode.right); // 對右子節點作左單旋 return roateRight(AvlNode); // 作右單旋 }
同理,如圖2,對B進行左單旋時,須要右子樹D的右子樹F的高度大於等於左子樹E的高度,不然須要進行雙旋;即先對B的右子節點D作右單旋,再對B作左單旋。實現代碼以下:get
function roateRightLeft(AvlNode) { AvlNode.left = roateRight(AvlNode.left); // 對左子節點作右單旋 return roateLeft(AvlNode); // 作左單旋 }
首先實現獲取樹高度的函數:
function getAvlTreeHeight(node) { if (node == null) { // node不存在返回0 return 0; } else { var leftHeight = getAvlTreeHeight(node.left); var rightHeight = getAvlTreeHeight(node.right); // 返回左子樹、右子樹中的最大高度 return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1; } }
實現平衡樹的函數:
function balance(node) { if (node == null) { return node; } // 左子樹高度比右子樹高度大1以上 if (getAvlTreeHeight(node.left) - getAvlTreeHeight(node.right) > 1) { if (getAvlTreeHeight(node.left.left) >= getAvlTreeHeight(node.left.right)) { // 若是左子樹的左子樹高度大於等於左子樹的右子樹高度 // 直接進行右單旋 node = roateRight(node); } else { // 不然須要右-左雙旋 node = roateRightLeft(node); } // 右子樹高度比左子樹高度大1以上 } else if (getAvlTreeHeight(node.right) - getAvlTreeHeight(node.left) > 1) { if (getAvlTreeHeight(node.right.right) >= getAvlTreeHeight(node.right.left)) { // 若是右子樹的右子樹高度大於等於右子樹的左子樹高度 // 直接進行左單旋 node = roateLeft(node); } else { // 不然須要左-右雙旋 node = roateLeftRight(node); } } return node; }
在二叉搜索樹的基礎上,每次插入節點,都須要作一次樹的平衡處理:
var insertNode = function(node, newNode){ if (newNode.key < node.key){ if (node.left === null){ node.left = newNode; // 插入節點後,作樹的平衡處理 node.left = balance(node.left); } else { insertNode(node.left, newNode); } } else { if (node.right === null){ node.right = newNode; // 插入節點後,作樹的平衡處理 node.right = balance(node.right); } else { insertNode(node.right, newNode); } } }
綜上,一顆自平衡AVL樹的原理及實現就完成了。