# 【資工考研】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 ![image](https://hackmd.io/_uploads/BJ-5WM0wyx.png) <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)