# 【資工考研】DS: Binary Tree (5) Red-Black Tree + Splay Tree
## Red-Black Tree
我們[前面](https://hackmd.io/@RyoukiWei/binary_tree_4)討論到 AVL tree 作為一種平衡的二元搜尋樹,改善了二元搜尋樹的痛點
紅黑樹同 AVL tree ,也是一種平衡的二元搜尋樹,但 AVL tree 是透過時刻控制樹高來達到平衡,而紅黑樹則很特別,它是透過控制節點顏色來平衡的
紅黑樹遵守:
1. 其節點以顏色區分,不是紅色就是黑色
2. root 必定是黑色
3. Nil 必定是黑色
4. 任一 path 上不可出現連續的紅色節點
5. root 到不同 leaf 之 path 上皆有相同數目的黑色節點 (其平衡的意義所在)
```cpp!
template <typename T>
struct RBNode
{
T data;
RBNode *left;
RBNode *right;
RBNode *parent;
bool isRed;
// All newly created nodes have a default color of red.
RBNode(const T &val) : data(val), left(nullptr), right(nullptr), parent(nullptr), isRed(true) {}
};
```
## Insert
我們先執行 BST 的插入方法,再對其做調整使之遵守 RB tree 的規則 (bottom up)
或著我們邊找插入位置邊調整 (top down)
想法基本上是:
1. 當發現某節點的兩子點為紅色時進行 color change (該節點變成紅色,兩子點變成黑色),檢查此刻有沒有出現連續的紅色節點 (該節點與其 parent 皆紅),若有則執行 rotation
2. 插入的新節點都先標成紅色 (可以發現我們 `RBNode` 的 constructor 是設定節點新建時顏色為紅色)
3. 如果有連續的紅色,則執行 rotation
4. root 顏色一律改成黑色
> [!Important]
> 其中第一步跟第三步可能這兩步都不會執行 rotation ,或著頂多其中一步會做一次 rotation
> 這是 RB tree 相較 AVL tree 的優勢所在,AVL tree 的 `erase` 可能會做 $O(\lg n)$ 次的 rotation ,但 RB tree 無論是 `insert` 還是 `erase` 都只會有 $O(1)$ 次 rotation
> [!Tip]
> RB tree 的 rotation 跟 AVL tree 的很像,基本上 AVL tree 那邊懂了,這邊也不成問題
> 只是 RB tree 是看顏色,AVL tree 看 balance factor
> RB tree 這邊的 LL, RR, LR, RL 判斷是自己跟父節點都紅色之下,從祖父節點看來
以下示範演算法課本提供的 bottom up 插入方法
下方是我們的調整方法:
我們要先判斷該節點的父節點 (`parent`) 是祖父的左子點還是右子點
因為 LL, RR, LR, RL 就是先看父節點位置再看該節點位置嘛
基本上 (LL, LR) 處理跟 (RR, RL) 的處理邏輯是一模一樣的,所以你會看到下面的 code 呈現上下雷同的感覺,我們就談 (LL, LR) 的處理:
case 1: 祖父的兩子點為紅色 (`parent` 跟 `uncle` 都是紅色),因為 `parent` 本來就是要紅色才需要調整,所以其實就是看 `uncle` 是不是也紅色
case 2: 連續兩紅色節點的處理
```cpp!
/**
* @brief In order to fix Red-Black Tree properties after insertion.
*/
void insertFixup(Node *node)
{
while (node != root && node->parent->isRed)
{
Node *parent = node->parent;
Node *grandparent = parent->parent;
if (parent == grandparent->left)
{
Node *uncle = grandparent->right;
// case 1
if (uncle && uncle->isRed)
{
// color change
parent->isRed = false;
uncle->isRed = false;
grandparent->isRed = true;
node = grandparent;
}
// case 2
else
{
// LR -> LL
if (node == parent->right)
{
node = parent;
leftRotate(node);
}
// for LL
parent->isRed = false;
grandparent->isRed = true;
rightRotate(grandparent);
}
}
else
{
Node *uncle = grandparent->left;
// case 3
if (uncle && uncle->isRed)
{
parent->isRed = false;
uncle->isRed = false;
grandparent->isRed = true;
node = grandparent;
}
// case 4
else
{
// RL -> RR
if (node == parent->left)
{
node = parent;
rightRotate(node);
}
// for RR
parent->isRed = false;
grandparent->isRed = true;
leftRotate(grandparent);
}
}
}
root->isRed = false; // Ensure root is black.
}
void leftRotate(Node *x)
{
// y will become the new root of the subtree.
Node *y = x->right;
x->right = y->left;
if (y->left != exNode)
y->left->parent = x;
y->parent = x->parent;
if (!x->parent) // root does not have parent
root = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->left = x;
x->parent = y;
}
void rightRotate(Node *y)
{
// x will become the new root of the subtree.
Node *x = y->left;
y->left = x->right;
if (x->right != exNode)
x->right->parent = y;
x->parent = y->parent;
if (!y->parent) // root does not have parent
root = x;
else if (y == y->parent->right)
y->parent->right = x;
else
y->parent->left = x;
x->right = y;
y->parent = x;
}
```
僅看插入的實現就相對簡單多了,難的都在 `insertFixup` :
```cpp!
/**
* @brief Inserts a value into the Red-Black Tree.
*
* @param val The value to be inserted.
* @return A pair where the first element is a pointer to the node containing the value,
* and the second element is a boolean indicating whether the insertion was successful.
*/
std::pair<Node *, bool> insert(const T &val)
{
Node *curr = root;
Node *parent = nullptr;
// Just BST insert
while (curr != exNode)
{
parent = curr;
if (val == curr->data)
return {curr, false}; // already exist
curr = val < curr->data ? curr->left : curr->right;
}
Node *node = new Node(val);
node->left = node->right = exNode;
node->parent = parent;
if (!parent)
root = node;
else if (val < parent->data)
parent->left = node;
else
parent->right = node;
if (!node->parent)
{
node->isRed = false;
return {node, true};
}
if (!node->parent->parent)
return {node, true};
insertFixup(node);
return {node, true};
}
```
### Erase
```cpp!
/**
* @brief Removes a value from the Red-Black Tree.
*
* @param val The value to be removed.
* @return The number of nodes removed (0 if the value is not found, 1 otherwise).
*/
size_t erase(const T &val)
{
Node *node = find(val);
if (node == exNode)
return 0; // Nothing found to be removed.
Node *temp, *child;
temp = (node->left == exNode || node->right == exNode)
? node
: findSuccssor(node);
child = temp->left != exNode ? temp->left : temp->right;
child->parent = temp->parent;
if (!temp->parent) // if removing root
root = child;
else if (temp = temp->parent->left)
temp->parent->left = child;
else
temp->parent->right = child;
if (temp != node) // copy the data if temp is a successor
node->data = temp->data;
if (!temp->isRed)
delFixup(child);
delete temp;
return 1;
}
```
```cpp!
void delFixup(Node *node)
{
while (node != root && !node->isRed)
{
Node *parent = node->parent;
if (node == parent->left)
{
Node *sibling = parent->right;
if (sibling->isRed)
{
sibling->isRed = false;
parent->isRed = true;
leftRotate(parent);
sibling = parent->right;
}
if (!sibling->left->isRed && !sibling->right->isRed)
{
sibling->isRed = true;
node = node->parent;
}
else
{
if (!sibling->right->isRed)
{
sibling->left->isRed = false;
sibling->isRed = true;
rightRotate(sibling);
sibling = parent->right;
}
sibling->isRed = parent->isRed;
parent->isRed = false;
sibling->right->isRed = false;
leftRotate(parent);
node = root;
}
}
else
{
Node *sibling = parent->left;
if (sibling->isRed)
{
sibling->isRed = false;
parent->isRed = true;
rightRotate(parent);
sibling = parent->left;
}
if (!sibling->left->isRed && !sibling->right->isRed)
{
sibling->isRed = true;
node = node->parent;
}
else
{
if (!sibling->left->isRed)
{
sibling->right->isRed = false;
sibling->isRed = true;
leftRotate(sibling);
sibling = parent->left;
}
sibling->isRed = parent->isRed;
parent->isRed = false;
sibling->left->isRed = false;
rightRotate(parent);
node = root;
}
}
}
node->isRed = false;
}
```
### 討論
> [!Important]
> 定義 black height $\mathrm{bh}(x) = x$ 到 leaf 之 path 上不含 $x$ 的黑色節點數目
> 則任意 $x$ 的子樹至少包含 $2^{\mathrm{bh}(x)} - 1$ 個 internal nodes
> [!Important]
> A RB tree with $n$ internal nodes has at most $\left\lceil 2\cdot \lg (n+1) \right\rceil$ tree height
根據 RB tree 的定義可知 $\mathrm{bh}(\mathrm{root}) \ge \frac{h}{2}$
$\Rightarrow n \ge 2^{\frac{h}{2}} - 1$
$\therefore h\le \left\lceil 2\cdot \lg (n+1) \right\rceil$
### 從 2-3-4 Tree 到 RB Tree
如果我們重新定義 RB tree 的顏色規則,其實 2-3-4 tree 是可以轉換成 RB tree 的:
1. link 的顏色非黑即紅
2. 若此 link 在原 2-3-4 tree 存在,標為黑色;反之,標為紅色
3. 任意 path 上不可出現連續的紅色 link
4. root 到不同 leaf 上的 path 上存在相同數目的黑色 link

<center>2-3-4 tree 轉換成 RB tree</center>
我們進一步探討:
在 2-3-4 tree 中我[們說過每個 external nodes 皆在同個 level ,這是其平衡的意義所在](https://hackmd.io/@RyoukiWei/binary_tree_4?stext=5828%3A52%3A0%3A1737530378%3A37Tg5H),對應的其實就是 RB tree 中 root 到 leaf 的 path 上有相同的黑色節點數量這件事,當然,我們在這邊轉換 2-3-4 tree 成 RB tree 塗色的是 link ,但不影響我們意會到其中的奧妙
如果 2-3-4 tree 全都是 2-Node ,則轉換出來的 RB tree 高 $\lg (n+1)$
如果全是 4-Node ,則轉換出來的 RB tree 高 $2\lg (n+1)$
## Splay Tree
Splay tree 也是 BST,它 worst case 仍會達 $O(n)$ ,但它的 amortized cost 是 $O(\lg n)$ (均攤下, worst case 頂多 $O(\lg n)$)
AVL tree 跟 RB tree 都仰賴在節點上記錄一些訊息用以嚴格地保持樹平衡
而 splay tree 不需要冗餘的資料,我們只要每次操作都對 splay 起點執行 `splay` 就好
在平攤分析之下,splay tree 的時間複雜度亦與 AVL tree 和 RB tree 表現相當
每次的 `splay` 操作是其最重要的靈魂,它會將 splay 起點變成 root
畢竟我們是二元**搜尋**樹,對於查找頻率高的元素,讓它越靠近 root 不應該效率會更好嗎?
不過 splay tree 除了還是有機會變非常 skewed 外,還有一個大缺點:
即便我們的操作是 read-only ,例如 `find` ,splay tree 的樹結構仍會改變
這使得 splay tree 在 multi-thread 之類的環境會顯得相當複雜,不易維護
別說 multi-thread ,原本幫 BST 寫 iterator 就不容易了,要你幫 splay tree 寫 iterator 才是真的頭痛至極 (大概是不適合還設計 iterator 給它啦)
`splay` 操作基本上我們分只看兩點跟看三點兩大 cases
只看兩點的情況很簡單,把 splay 起點往上變 root 就好
看三點的話,就還要同時維持 BST 的大小關係,但一樣是把 splay 起點變 root ,相當樸實無華
當然,人家有定義這個 `splay` 分 `zig` 跟 `zag` 兩種 rotation 方式,就好像 AVL tree 他們有定義 left rotation 跟 right rotation
不過我想這邊我們不必細聊太多
我發現[中文 wiki](https://zh.wikipedia.org/zh-tw/%E4%BC%B8%E5%B1%95%E6%A0%91) 可以參考,真的有興趣可以去看
## 【資工考研】DS: Binary Tree 全系列
- [【資工考研】DS: Binary Tree (1)](https://hackmd.io/@RyoukiWei/binary_tree_1)
- [【資工考研】DS: Binary Tree (2)](https://hackmd.io/@RyoukiWei/binary_tree_2)
- [【資工考研】DS: Binary Tree (3)](https://hackmd.io/@RyoukiWei/binary_tree_3)
- [【資工考研】DS: Binary Tree (4)](https://hackmd.io/@RyoukiWei/binary_tree_4)
- [【資工考研】DS: Binary Tree (5)](https://hackmd.io/@RyoukiWei/binary_tree_5)