前面介紹了二叉樹和二叉樹搜索樹的創建和使用,接下來我們繼續學習關於樹的更多知識。
BST存在一個問題,就是當我們多次添加節點數,有可能造成一種情況,樹的一條邊可能會非常深,有非常多的層,而另一條分支卻只有幾層。當我們需要進行添加、移除和搜索某一節點時,可能會引起一些性能問題。爲了解決這類問題,我們進行自平衡樹的學習。自平衡樹常見有兩種:AVL樹和紅黑樹。
自平衡樹
準備知識
節點的高度和平衡因子
節點高度:從節點到任意子節點的彼岸的最大值。這個相對來說容易理解。那麼獲得節點高度的代碼實現如下:
getNodeHeight(node) {
if (node == null) {
return -1;
}
return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) + 1;
}
平衡因子:每個節點左子樹高度和右子樹高度的差值。該值爲0 、 -1、 1 時則爲正常值,說明該二叉樹已經平衡。若果該值不是這三個值之一,則需要平衡該樹。遵循計算一個節點的平衡因子並返回其值的代碼如下:
const BalanceFactor = {
UNBALANCED_RIGHT: 1,
SLIGHTLY_UNBALANCED_RIGHT: 2,
BALANCED: 3,
SLIGHTLY_UNBALANCED_LEFT: 4,
UNBALANCED_LEFT: 5
};
getBalanceFactor(node) {
const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
switch (heightDifference) {
case -2:
return BalanceFactor.UNBALANCED_RIGHT;
case -1:
return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT;
case 1:
return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT;
case 2:
return BalanceFactor.UNBALANCED_LEFT;
default:
return BalanceFactor.BALANCED;
}
}
AVL樹
AVL樹是一種自平衡樹,添加或移除節點時,AVL會嘗試保持自平衡,也就是會可能嘗試轉換爲完全樹。接下來介紹平衡樹進行自平衡的操作,AVL旋轉
AVL旋轉
在對AVL進行添加或者移除節點後,我們需要計算節點的高度並驗證是否需要進行平衡。旋轉操作分爲單旋轉和雙旋轉兩種。
左-左LL(向右的單旋轉)
/**
* Left left case: rotate right
*
* b a
* / \ / \
* a e -> rotationLL(b) -> c b
* / \ / \
* c d d e
*
* @param node Node<T>
*/
rotationLL(node){
const tmp = node.right;
node.left = tmp.right;
tmp.right = node;
return tmp;
}
右-右RR(向左的單旋轉)
/**
* Right right case: rotate left
*
* a b
* / \ / \
* c b -> rotationRR(a) -> a e
* / \ / \
* d e c d
*
* @param node Node<T>
*/
rotationLL(node) {
const tmp = node.left;
node.left = tmp.right;
tmp.right = node;
return tmp;
}
左-右(LR):向右的雙旋轉
/**
* Left right case: rotate left then right
* @param node Node<T>
*/
rotationLR(node) {
node.left = this.rotationRR(node.left);
return this.rotationLL(node);
}
右-左(RL):向左的雙旋轉
/**
* Right left case: rotate right then left
* @param node Node<T>
*/
rotationRL(node) {
node.right = this.rotationLL(node.right);
return this.rotationRR(node);
}
完成平衡操作(旋轉)的學習後,我們接下來完善一下AVL樹添加或者移除節點的操作
添加節點
insert(key) {
this.root = this.insertNode(this.root, key);
}
insertNode(node, key) {
if (node == null) {
return new Node(key);
} if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
node.left = this.insertNode(node.left, key);
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
node.right = this.insertNode(node.right, key);
} else {
return node; // duplicated key
}
// verify if tree is balanced
const balanceFactor = this.getBalanceFactor(node);
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
// Left left case
node = this.rotationLL(node);
} else {
// Left right case
return this.rotationLR(node);
}
}
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
// Right right case
node = this.rotationRR(node);
} else {
// Right left case
return this.rotationRL(node);
}
}
return node;
}
移除節點
removeNode(node, key) {
node = super.removeNode(node, key); // {1}
if (node == null) {
return node;
}
// verify if tree is balanced
const balanceFactor = this.getBalanceFactor(node);
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
// Left left case
if (
this.getBalanceFactor(node.left) === BalanceFactor.BALANCED
|| this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
) {
return this.rotationLL(node);
}
// Left right case
if (this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
return this.rotationLR(node.left);
}
}
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
// Right right case
if (
this.getBalanceFactor(node.right) === BalanceFactor.BALANCED
|| this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
) {
return this.rotationRR(node);
}
// Right left case
if (this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
return this.rotationRL(node.right);
}
}
return node;
}
}
以上就是關於AVL樹基礎知識的學習,接下來我們介紹另一種平衡樹——紅黑樹。
和AVL樹一樣,紅黑樹也是一個自平衡二叉樹。紅黑樹本質上也是AVL樹,但可以包含多次插入和刪除。在紅黑樹中,每個節點都遵循以下規則:
- 顧名思義,每個節點不是紅的就是黑的;
- 樹的根節點就是黑的;
- 所有葉節點都是黑的;
- 如果一個節點是紅的,那麼他的兩個子節點都是黑的
- 不能有兩個相鄰的紅節點,一個紅節點不能有紅的父節點或子節點;
- 從給定的節點到他的後代節點(NULL葉節點)的所有路徑包含相同數量的黑色節點。
紅黑樹
創建紅黑樹的骨架
const BalanceFactor = {
UNBALANCED_RIGHT: 1,
SLIGHTLY_UNBALANCED_RIGHT: 2,
BALANCED: 3,
SLIGHTLY_UNBALANCED_LEFT: 4,
UNBALANCED_LEFT: 5
};
//定義顏色類
const Colors = {
RED:'red',
BLACK:'black'
};
//創建紅黑樹的節點類型
class RedBlackNode extends Node{
constructor(key){
super(key);
this.key = key;
this.color = Colors.RED;
this.parent = null;
}
isRed(){
return this.color === Colors.RED;
}
};
class RedBlackTree extends BinarySearchTree{
constructor(compareFn = defaultCompare){
super(compareFn);
this.compareFn = compareFn;
this.root = null;
};
}
旋轉操作
向右單旋轉
//rotationLL
static rotationLL(node){
const tmp = node.left;
node.left = tmp.right;
if(tmp.right && tmp.right.key){
tmp.right.parent = node;
}
tmp.right.parent = node.parent;
if (!node.parent){
this.root = tmp;
}else{
if(node === node.parent.left){
node.parent.left = tmp;
}else{
node.parent.right = tmp;
}
tmp.right = node;
node.parent = tmp;
}
};
向左單旋轉
//rotationRR
static rotationRR(node){
const tmp = node.right;
node.right = tmp.left;
if (tmp.left && tmp.left.key){
tmp.left.parent = node;
}
tmp.parent = node.parent;
if (!node.parent){
this.root = tmp;
}else{
if(node === node.parent.left){
node.parent.left = tmp;
}else{
node.parent.right = tmp;
}
}
tmp.left = node;
node.parent = tmp;
}
驗證節點顏色屬性
//插入節點後驗證紅黑樹的屬性
static fixTreeProperties(node){
while (node && node.parent && node.parent.color.isRed() && node.color !== Colors.BLACK){
let parent = node.parent;
const grandParent = parent.parent;
//case A:父節點是左側子節點
if (grandParent && grandParent.left === parent){
const uncle = grandParent.right;
//case 1A:叔節點也是紅色——只需要重新填色
if (uncle && uncle.color === Colors.RED){
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncle.color = Colors.BLACK;
node = grandParent;
}else{
// case 2A:節點是右側子節點——右旋轉
if (node === parent.left){
RedBlackTree.rotationRR(parent);
node = parent;
parent = node.parent;
}
//case 3A:子節點是左側子節點——左旋轉
else if(node === parent.right){
RedBlackTree.rotationRR(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
}
}
//case B:父節點是右側子節點
else{
const uncle = grandParent.left;
//case1B:叔節點是紅色——只需要重新填色
if(uncle && uncle.color === Colors.RED){
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncle.color = Colors.BLACK;
node = grandParent;
}
//case2B:節點是左側子節點—右旋轉
if (node === parent.left){
RedBlackTree.rotationLL(parent);
node = parent;
parent = node.parent;
}
//case3B:節點是右側子節點——左旋轉
else if(node === parent.right){
RedBlackTree.rotationRR(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
}
this.root.color = Colors.BLACK;
}
}
添加新節點
//向紅黑樹插入新節點
insertNode(node,key){
if(this.compareFn(key,node.key) === Compare.LESS_THAN){
if(node.left === null){
node.left = new RedBlackNode(key);
node.left.parent = node;
return node.left;
}
else{
return this.insertNode(node.left,key);
}
}
else if(node.right === null){
node.right = new RedBlackNode(key);
node.right.parent = node;
return node.right;
}
};
insert(key) {
if (this.root === null){
this.root = new RedBlackNode(key);
this.root.color = Colors.BLACK;
}else{
const newNode = this.insertNode(this.root, key);
RedBlackTree.fixTreeProperties(newNode);
}
};