簡述容器之rb_tree及其應用--(boolan)
阿新 • • 發佈:2019-02-16
- 概述:
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可以重複。
- 紅黑樹的基本操作是新增、刪除和旋轉。在對紅黑樹進行新增或者刪除後,會用到旋轉方法。因為新增或刪除紅黑樹中的節點之後,紅黑樹就發生了變化,可能不滿足紅黑樹的五條特性,也就不再是一顆紅黑樹了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅黑樹。簡單的說旋轉的目的是為了讓樹保持紅黑樹的特性。
以下為參考程式碼:
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,但是使用方法上與之前的都是一樣的,只是換了一個名字而已。