1. 程式人生 > >簡述容器之rb_tree及其應用--(boolan)

簡述容器之rb_tree及其應用--(boolan)

  • 概述:
      Red-Black tree(紅黑樹)是平衡二分搜尋樹(balanced binary search tree)中常被使用的一種平衡二分搜尋樹的特徵:排列規則有利於search和insert,並保持適度平衡————無任何結點太深。
       紅黑樹只要求區域性平衡,C++ STL中的map、set、multimap、multiset都應用了紅黑樹。
      紅黑樹的每個節點或者上都有儲存位表示節點的顏色,顏色是紅或者黑,紅黑樹的特徵有:1.每個節點或者是黑色或者是紅色 2.根節點是黑色 3.每個葉子節點是黑色[這裡葉子節點,是指為空的葉子節點] 4.如果一個節點是黑色的,那麼它的子節點必須是黑色的 5.從一個節點到該節點的子節點的所有路徑上包含相同數目的黑節點[注意:這裡需要確保沒有一條路徑會比其他路徑長出兩倍]

  rb_tree提供“遍歷”操作及iterators。按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。
  我們不應使用rb_tree的iterators改變元素值(因為元素有其嚴謹的排列規則)。程式設計裡面(programming leve)並未阻絕此事。如此設計是正確的,因為rb_tree即將為set和map服務(作為其底部支援),而map允許元素的data被改變,只有元素的key才是不可被改變的。
  rb_tree提供兩種insertion操作:insert_unique()和insert_equal前者表示節點的key 一定在整個tree中獨一無二,否則安插失敗,後者表示節點的key可以重複。

image

  •   紅黑樹的基本操作是新增、刪除和旋轉。在對紅黑樹進行新增或者刪除後,會用到旋轉方法。因為新增或刪除紅黑樹中的節點之後,紅黑樹就發生了變化,可能不滿足紅黑樹的五條特性,也就不再是一顆紅黑樹了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅黑樹。簡單的說旋轉的目的是為了讓樹保持紅黑樹的特性。
    以下為參考程式碼:
enum RBTColor{RED, BLACK};

template <class T>
class RBTNode{
    public:
        RBTColor color;    // 顏色
        T key;            // 關鍵字(鍵值)
RBTNode *left; // 左孩子 RBTNode *right; // 右孩子 RBTNode *parent; // 父結點 RBTNode(T value, RBTColor c, RBTNode *p, RBTNode *l, RBTNode *r): key(value),color(c),parent(),left(l),right(r) {} }; template <class T> class RBTree { private: RBTNode<T> *mRoot; // 根結點 public: RBTree(); ~RBTree(); // 前序遍歷"紅黑樹" void preOrder(); // 中序遍歷"紅黑樹" void inOrder(); // 後序遍歷"紅黑樹" void postOrder(); // (遞迴實現)查詢"紅黑樹"中鍵值為key的節點 RBTNode<T>* search(T key); // (非遞迴實現)查詢"紅黑樹"中鍵值為key的節點 RBTNode<T>* iterativeSearch(T key); // 查詢最小結點:返回最小結點的鍵值。 T minimum(); // 查詢最大結點:返回最大結點的鍵值。 T maximum(); // 找結點(x)的後繼結點。即,查詢"紅黑樹中資料值大於該結點"的"最小結點"。 RBTNode<T>* successor(RBTNode<T> *x); // 找結點(x)的前驅結點。即,查詢"紅黑樹中資料值小於該結點"的"最大結點"。 RBTNode<T>* predecessor(RBTNode<T> *x); // 將結點(key為節點鍵值)插入到紅黑樹中 void insert(T key); // 刪除結點(key為節點鍵值) void remove(T key); // 銷燬紅黑樹 void destroy(); // 列印紅黑樹 void print(); private: // 前序遍歷"紅黑樹" void preOrder(RBTNode<T>* tree) const; // 中序遍歷"紅黑樹" void inOrder(RBTNode<T>* tree) const; // 後序遍歷"紅黑樹" void postOrder(RBTNode<T>* tree) const; // (遞迴實現)查詢"紅黑樹x"中鍵值為key的節點 RBTNode<T>* search(RBTNode<T>* x, T key) const; // (非遞迴實現)查詢"紅黑樹x"中鍵值為key的節點 RBTNode<T>* iterativeSearch(RBTNode<T>* x, T key) const; // 查詢最小結點:返回tree為根結點的紅黑樹的最小結點。 RBTNode<T>* minimum(RBTNode<T>* tree); // 查詢最大結點:返回tree為根結點的紅黑樹的最大結點。 RBTNode<T>* maximum(RBTNode<T>* tree); // 左旋 void leftRotate(RBTNode<T>* &root, RBTNode<T>* x); // 右旋 void rightRotate(RBTNode<T>* &root, RBTNode<T>* y); // 插入函式 void insert(RBTNode<T>* &root, RBTNode<T>* node); // 插入修正函式 void insertFixUp(RBTNode<T>* &root, RBTNode<T>* node); // 刪除函式 void remove(RBTNode<T>* &root, RBTNode<T> *node); // 刪除修正函式 void removeFixUp(RBTNode<T>* &root, RBTNode<T> *node, RBTNode<T> *parent); // 銷燬紅黑樹 void destroy(RBTNode<T>* &tree); // 列印紅黑樹 void print(RBTNode<T>* tree, T key, int direction); #define rb_parent(r) ((r)->parent) #define rb_color(r) ((r)->color) #define rb_is_red(r) ((r)->color==RED) #define rb_is_black(r) ((r)->color==BLACK) #define rb_set_black(r) do { (r)->color = BLACK; } while (0) #define rb_set_red(r) do { (r)->color = RED; } while (0) #define rb_set_parent(r,p) do { (r)->parent = (p); } while (0) #define rb_set_color(r,c) do { (r)->color = (c); } while (0) }; /* * 對紅黑樹的節點(x)進行左旋轉 * * 左旋示意圖(對節點x進行左旋): * px px * / / * x y * / \ --(左旋)--> / \ # * lx y x ry * / \ / \ * ly ry lx ly * * */ template <class T> void RBTree<T>::leftRotate(RBTNode<T>* &root, RBTNode<T>* x) { // 設定x的右孩子為y RBTNode<T> *y = x->right; // 將 “y的左孩子” 設為 “x的右孩子”; // 如果y的左孩子非空,將 “x” 設為 “y的左孩子的父親” x->right = y->left; if (y->left != NULL) y->left->parent = x; // 將 “x的父親” 設為 “y的父親” y->parent = x->parent; if (x->parent == NULL) { root = y; // 如果 “x的父親” 是空節點,則將y設為根節點 } else { if (x->parent->left == x) x->parent->left = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” else x->parent->right = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” } // 將 “x” 設為 “y的左孩子” y->left = x; // 將 “x的父節點” 設為 “y” x->parent = y; } /* * 對紅黑樹的節點(y)進行右旋轉 * * 右旋示意圖(對節點y進行左旋): * py py * / / * y x * / \ --(右旋)--> / \ # * x ry lx y * / \ / \ # * lx rx rx ry * */ template <class T> void RBTree<T>::rightRotate(RBTNode<T>* &root, RBTNode<T>* y) { // 設定x是當前節點的左孩子。 RBTNode<T> *x = y->left; // 將 “x的右孩子” 設為 “y的左孩子”; // 如果"x的右孩子"不為空的話,將 “y” 設為 “x的右孩子的父親” y->left = x->right; if (x->right != NULL) x->right->parent = y; // 將 “y的父親” 設為 “x的父親” x->parent = y->parent; if (y->parent == NULL) { root = x; // 如果 “y的父親” 是空節點,則將x設為根節點 } else { if (y == y->parent->right) y->parent->right = x; // 如果 y是它父節點的右孩子,則將x設為“y的父節點的右孩子” else y->parent->left = x; // (y是它父節點的左孩子) 將x設為“x的父節點的左孩子” } // 將 “y” 設為 “x的右孩子” x->right = y; // 將 “y的父節點” 設為 “x” y->parent = x; } /* * 將結點插入到紅黑樹中 * * 引數說明: * root 紅黑樹的根結點 * node 插入的結點 // 對應《演算法導論》中的node */ template <class T> void RBTree<T>::insert(RBTNode<T>* &root, RBTNode<T>* node) { RBTNode<T> *y = NULL; RBTNode<T> *x = root; // 1. 將紅黑樹當作一顆二叉查詢樹,將節點新增到二叉查詢樹中。 while (x != NULL) { y = x; if (node->key < x->key) x = x->left; else x = x->right; } node->parent = y; if (y!=NULL) { if (node->key < y->key) y->left = node; else y->right = node; } else root = node; // 2. 設定節點的顏色為紅色 node->color = RED; // 3. 將它重新修正為一顆二叉查詢樹 insertFixUp(root, node); } /* * 將結點(key為節點鍵值)插入到紅黑樹中 * * 引數說明: * tree 紅黑樹的根結點 * key 插入結點的鍵值 */ template <class T> void RBTree<T>::insert(T key) { RBTNode<T> *z=NULL; // 如果新建結點失敗,則返回。 if ((z=new RBTNode<T>(key,BLACK,NULL,NULL,NULL)) == NULL) return ; insert(mRoot, z); } /* * 紅黑樹插入修正函式 * * 在向紅黑樹中插入節點之後(失去平衡),再呼叫該函式; * 目的是將它重新塑造成一顆紅黑樹。 * * 引數說明: * root 紅黑樹的根 * node 插入的結點 // 對應《演算法導論》中的z */ template <class T> void RBTree<T>::insertFixUp(RBTNode<T>* &root, RBTNode<T>* node) { RBTNode<T> *parent, *gparent; // 若“父節點存在,並且父節點的顏色是紅色” while ((parent = rb_parent(node)) && rb_is_red(parent)) { gparent = rb_parent(parent); //若“父節點”是“祖父節點的左孩子” if (parent == gparent->left) { // Case 1條件:叔叔節點是紅色 { RBTNode<T> *uncle = gparent->right; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } // Case 2條件:叔叔是黑色,且當前節點是右孩子 if (parent->right == node) { RBTNode<T> *tmp; leftRotate(root, parent); tmp = parent; parent = node; node = tmp; } // Case 3條件:叔叔是黑色,且當前節點是左孩子。 rb_set_black(parent); rb_set_red(gparent); rightRotate(root, gparent); } else//若“z的父節點”是“z的祖父節點的右孩子” { // Case 1條件:叔叔節點是紅色 { RBTNode<T> *uncle = gparent->left; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } // Case 2條件:叔叔是黑色,且當前節點是左孩子 if (parent->left == node) { RBTNode<T> *tmp; rightRotate(root, parent); tmp = parent; parent = node; node = tmp; } // Case 3條件:叔叔是黑色,且當前節點是右孩子。 rb_set_black(parent); rb_set_red(gparent); leftRotate(root, gparent); } } // 將根節點設為黑色 rb_set_black(root); } /* * 刪除結點(node),並返回被刪除的結點 * * 引數說明: * root 紅黑樹的根結點 * node 刪除的結點 */ template <class T> void RBTree<T>::remove(RBTNode<T>* &root, RBTNode<T> *node) { RBTNode<T> *child, *parent; RBTColor color; // 被刪除節點的"左右孩子都不為空"的情況。 if ( (node->left!=NULL) && (node->right!=NULL) ) { // 被刪節點的後繼節點。(稱為"取代節點") // 用它來取代"被刪節點"的位置,然後再將"被刪節點"去掉。 RBTNode<T> *replace = node; // 獲取後繼節點 replace = replace->right; while (replace->left != NULL) replace = replace->left; // "node節點"不是根節點(只有根節點不存在父節點) if (rb_parent(node)) { if (rb_parent(node)->left == node) rb_parent(node)->left = replace; else rb_parent(node)->right = replace; } else // "node節點"是根節點,更新根節點。 root = replace; // child是"取代節點"的右孩子,也是需要"調整的節點"。 // "取代節點"肯定不存在左孩子!因為它是一個後繼節點。 child = replace->right; parent = rb_parent(replace); // 儲存"取代節點"的顏色 color = rb_color(replace); // "被刪除節點"是"它的後繼節點的父節點" if (parent == node) { parent = replace; } else { // child不為空 if (child) rb_set_parent(child, parent); parent->left = child; replace->right = node->right; rb_set_parent(node->right, replace); } replace->parent = node->parent; replace->color = node->color; replace->left = node->left; node->left->parent = replace; if (color == BLACK) removeFixUp(root, child, parent); delete node; return ; } if (node->left !=NULL) child = node->left; else child = node->right; parent = node->parent; // 儲存"取代節點"的顏色 color = node->color; if (child) child->parent = parent; // "node節點"不是根節點 if (parent) { if (parent->left == node) parent->left = child; else parent->right = child; } else root = child; if (color == BLACK) removeFixUp(root, child, parent); delete node; } /* * 刪除紅黑樹中鍵值為key的節點 * * 引數說明: * tree 紅黑樹的根結點 */ template <class T> void RBTree<T>::remove(T key) { RBTNode<T> *node; // 查詢key對應的節點(node),找到的話就刪除該節點 if ((node = search(mRoot, key)) != NULL) remove(mRoot, node); } /* * 紅黑樹刪除修正函式 * * 在從紅黑樹中刪除插入節點之後(紅黑樹失去平衡),再呼叫該函式; * 目的是將它重新塑造成一顆紅黑樹。 * * 引數說明: * root 紅黑樹的根 * node 待修正的節點 */ template <class T> void RBTree<T>::removeFixUp(RBTNode<T>* &root, RBTNode<T> *node, RBTNode<T> *parent) { RBTNode<T> *other; while ((!node || rb_is_black(node)) && node != root) { if (parent->left == node) { other = parent->right; if (rb_is_red(other)) { // Case 1: x的兄弟w是紅色的 rb_set_black(other); rb_set_red(parent); leftRotate(root, parent); other = parent->right; } if ((!other->left || rb_is_black(other->left)) && (!other->right || rb_is_black(other->right))) { // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->right || rb_is_black(other->right)) { // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 rb_set_black(other->left); rb_set_red(other); rightRotate(root, other); other = parent->right; } // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->right); leftRotate(root, parent); node = root; break; } } else { other = parent->left; if (rb_is_red(other)) { // Case 1: x的兄弟w是紅色的 rb_set_black(other); rb_set_red(parent); rightRotate(root, parent); other = parent->left; } if ((!other->left || rb_is_black(other->left)) && (!other->right || rb_is_black(other->right))) { // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->left || rb_is_black(other->left)) { // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 rb_set_black(other->right); rb_set_red(other); leftRotate(root, other); other = parent->left; } // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->left); rightRotate(root, parent); node = root; break; } } } if (node) rb_set_black(node); }
  •   容器set,multiset
      set/multiset以rb_tree為底層結構,因此有‘元素自動排序’特性。排序的依據是key,而set/multiset元素的key和value合一:key就是value。
      set/multiset提供“遍歷”操作及iterators按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。
      我們無法使用set/multiset的iterators改變元素值(因為key有其嚴謹的排列規則)。set/multiset的iterator是其底部的RB tree的const-iterator,就是為了禁止user對元素賦值。
      set元素的key必須獨一無二,因此其insert()用的是re_tree的insert_unique().multiset元素的key可以重複,因此其insert()用的是rb_tree的insert_equal()。
      set的所有操作都是通過呼叫底層的紅黑樹進行操作。
  •   容器map,multimap
      map/multimap以rb_tree為底層結構,因此有‘元素自動排序’特性。排序的依據是key。
      map/multimap提供“遍歷”操作及iterators按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。
      我們無法使用map/multimap的iterators改變元素的key(因為key有其嚴謹的排列規則),但可以用它來改變元素的data。因此map/multimap內部自動將user指定的key type設為const,如此便能禁止user對元素的key賦值。
      map的key必須是獨一無二的,因此insert()用的是rb_tree的insert_unique()。multimap元素的key可以重複,因此其insert()用的是rb_tree的insert_equal().
      注意:容器map,獨有的operator[],但是multimap不可以使用[]進行insert操作。
  • 容器hashtable
      我們可以使用hashtable iterators 改變元素的data,但不能改變元素的key(因為hashtable根據key實現嚴謹的元素排列)。到了C++11之後所有的hash開頭的都變成了unordered。導致上層的set和map都換了名字,換成了容器unordered_set,unordered_multiset;和容器unordered_map,unordered_multimap,但是使用方法上與之前的都是一樣的,只是換了一個名字而已。