本文主要包括如下內容:node
爲何須要平衡二叉樹?git
經過前面的 二分搜索樹(Binary Search Tree)和 BinarySearchTree的時間複雜度分析 的介紹咱們知道,二分搜索樹的性能跟樹的高度(h)有關係 :github
h 爲二分搜索樹的高度,那麼高度 h 和二分搜索樹節點數 n 的關係是什麼呢?bash
分析下滿的二叉樹的狀況就知道了節點數量和二叉樹高度的的關係了:性能
層數 | 該層的節點數 |
---|---|
0層 | 1 |
1層 | 2 |
2層 | 4 |
3層 | 8 |
4層 | 16 |
h-1層 | 2^(h-1) |
那麼一個h層的滿二叉樹總共有多少節點呢?就是每層的元素個數相加便可:ui
n = 2^0+2^1+2^3+2^4+...+2^(h-1) = 2^h - 1spa
用對數表示就是:h = log(n+1).net
用大O表示法就是: O(h) = O(log n)code
上面是基於 滿二叉樹 的狀況,因此二分搜索樹最好狀況的時間複雜度爲 O(log n)cdn
可是根據二分搜索樹的性質知道,在最壞的狀況二分搜索樹會退化成鏈表,那麼二分搜索樹的在最壞的狀況的時間複雜度爲 O(n).
二分搜索樹的最好狀況的 O(log n) 和 最壞狀況的 O(n) 是個什麼概念呢?下面用一個表格對比下:
對比下 n 和 log(n) 之間的差距
n | log(n) | 差距 |
---|---|---|
16 | 4 | 4 倍 |
1024 | 10 | 100倍 |
100w | 20 | 5萬倍 |
隨着數量不斷的加大,它們之間性能的差距不斷的兩極分化。
這個時候就須要一個可以平衡的二分搜索樹,就算在最壞的狀況也能保證二分搜索樹的性能保持在 O(log n)
那麼什麼是平衡二叉樹,平衡二叉樹 也稱 平衡二分搜索樹(Balanced Binary Tree)是一種結構平衡的二分搜索樹。
平衡二叉樹由二分搜索樹發展而來,在二分搜索樹的基礎上平衡二叉樹須要知足兩個條件:
常見的平衡二叉搜索樹有:
下面咱們介紹下出現最先的平衡二叉樹 AVL樹。
AVL樹 是由 G. M. Adelson- V elsky 和 E. M. Landis於1962年提出。AVL樹是最先的平衡二叉樹。
AVL樹維護自身的平衡涉及到兩個概念:
節點的高度就是從根節點到該節點的邊的總和
節點的 平衡因子 是左子樹的高度減去它的右子樹的高度
帶有平衡因子一、0或 -1的節點被認爲是平衡的,由於它的左右子樹高度差不超過 1
以下面一顆 AVL樹:
上圖的AVL樹中,節點最大的平衡因子是1,因此它是一顆平衡二叉樹。
一顆平衡二叉樹的平衡性被打破確定是在插入或者刪除的時候,下面就來看如何在插入和刪除的時候保持AVL樹的平衡性。
以下面一顆AVL樹,在插入節點 5 後 節點 15 的平衡因子變成了 2,樹的平衡性被打破:
這種狀況咱們稱之爲 插入的元素在不平衡節點左側的左側 簡稱 LL
遇到該狀況須要對不平衡的節點進行右旋轉:
通用狀況以下:
右旋轉代碼:
private Node<K, V> rotateRight(Node<K, V> node) {
Node<K, V> nodeLeft = node.left;
Node<K, V> lRight = nodeLeft.right;
//右旋轉
nodeLeft.right = node;
node.left = lRight;
//維護節點高度
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
nodeLeft.height = 1 + Math.max(getHeight(nodeLeft.left), getHeight(nodeLeft.right));
return nodeLeft;
}
複製代碼
這種狀況也就是上一個狀況的鏡像。它須要對不平衡的節點向左旋轉:
左旋轉代碼:
private Node<K, V> rotateLeft(Node<K, V> node) {
Node<K, V> nodeRight = node.right;
Node<K, V> rLeft = nodeRight.left;
//左旋轉
nodeRight.left = node;
node.right = rLeft;
//維護節點高度
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
nodeRight.height = 1 + Math.max(getHeight(nodeRight.left), getHeight(nodeRight.right));
return nodeRight;
}
複製代碼
插入的元素在不平衡節點的左側的右側,以下圖所示:
這個時候就不能單純的對節點 12 右旋轉,11和12都比10要大。這種狀況須要兩次旋轉:
插入的元素在不平衡節點的右側的左側,以下圖所示:
插入操做維護AVL平衡性的相關代碼:
public void add(K key, V value) {
root = _add(root, key, value);
}
private Node<K, V> _add(Node<K, V> node, K key, V value) {
if (node == null) {
size++;
return new Node<>(key, value);
}
if (key.compareTo(node.key) < 0)
node.left = _add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = _add(node.right, key, value);
else //若是已經存在,修改對應value的值
node.value = value;
//維護node的高度
//左右子樹最高的高度+1
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
//獲取節點的平衡因子
int balanceFactor = getBalanceFactor(node);
// 右旋轉
// 左子樹比右子樹要高超過了1,說明當前節點的平衡被打破
// 且新添加的節點是在左子樹的左子樹的左側
//LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rotateRight(node);
//RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return rotateLeft(node);
//LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = rotateLeft(node.left);//轉化LL形式
return rotateRight(node);
}
//RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rotateRight(node.right);//轉化成RR
return rotateLeft(node);
}
return node;
}
複製代碼
刪除操做和插入操做須要保持平衡的狀況基本是同樣的,代碼以下所示:
private Node<K, V> remove(Node<K, V> node, K key) {
if (node == null) {
return null;
}
Node<K, V> retNode = null;
//若是要刪除的節點小於當前節點,繼續查詢其左子樹
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
retNode = node;
}
//若是要刪除的節點大於當前節點,繼續查詢其右子樹
else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
retNode = node;
}
//要刪除的節點就是當前的節點
else {
//若是要刪除節點的左子樹爲空
if (node.left == null) {
Node<K, V> rightNode = node.right;
node.right = null;
size--;
retNode = rightNode;
}
//若是要刪除節點的右子樹爲空
else if (node.right == null) {
Node<K, V> leftNode = node.left;
node.left = null;
size--;
retNode = leftNode;
}
//=======若是要刪除的節點左右子樹都不爲空
else {
//找到要刪除節點的後繼,也就是右子樹的最小值
Node<K, V> successor = getMin(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
}
//若是刪除的節點是葉子節點
if (retNode == null) {
return null;
}
//獲得retNode以後,維護平衡性
//維護node的高度
//左右子樹最高的高度+1
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
//獲取節點的平衡因子
int balanceFactor = getBalanceFactor(retNode);
// 右旋轉
// 左子樹比右子樹要高超過了1,說明當前節點的平衡被打破
// 且新添加的節點是在左子樹的左子樹的左側
//LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rotateRight(retNode);
//RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return rotateLeft(retNode);
//LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = rotateLeft(retNode.left);//轉化LL形式
return rotateRight(retNode);
}
//RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rotateRight(retNode.right);//轉化成RR
return rotateLeft(retNode);
}
return retNode;
}
複製代碼
下面是個人公衆號,乾貨文章不錯過,有須要的能夠關注下,很是感謝: