树及树的算法(4) —— 红黑树
来源:互联网 发布:怎么开放端口 编辑:程序博客网 时间:2024/05/21 10:33
红黑树是在1972年由德国科学家鲁道夫·贝尔发明的,他称之为“对称二叉B树”,它现代的名字是在 Leo J. Guibas和 Robert Sedgewick 于1978年写的一篇论文中获得的。红黑树的实现是很复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
图:鲁道夫·贝尔
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用如实时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他数据结构中作为建造板块的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树。
红黑树在函数式编程中也特别有用,在这里它们是最常用的持久数据结构之一,它们用来构造关联数组和集合,在突变之后它们能保持为以前的版本。除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。
红黑树是 2-3-4树的一种等同。换句话说,对于每个 2-3-4树,都存在至少一个数据元素是同样次序的红黑树。在 2-3-4树上的插入和删除操作也等同于在红黑树中颜色翻转和旋转。这使得 2-3-4树成为理解红黑树背后的逻辑的重要工具,这也是很多介绍算法的教科书在红黑树之前介绍 2-3-4树的原因,尽管 2-3-4树在实践中不经常使用。
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
enum NODECOLOR{RED = 0,BLACK = 1};//红黑树class RBTree : public BST{private:NODECOLOR color;RBTree* parent; public:RBTree();RBTree* Parent();RBTree* GrandParent();RBTree* Uncle(); RBTree* Sibling(); void AttachKey (Object& object, RBTree& parent); void Insert( Object& object, RBTree* prt = 0 ); void Withdraw (Object& object);protected: void DeleteBalance(RBTree *n);void RotateLeft();void RotateRight();private: void insert_case1(RBTree& n); void insert_case2(RBTree& n); void insert_case3(RBTree& n); void insert_case4(RBTree& n); void insert_case5(RBTree& n); void delete_case1(RBTree& n); void delete_case2(RBTree& n); void delete_case3(RBTree& n); void delete_case4(RBTree& n); void delete_case5(RBTree& n); void delete_case6(RBTree& n);bool isRBLeaf();};RBTree::RBTree():BST(),parent(0),color(BLACK){}//找妈妈RBTree* RBTree::Parent(){if (0 == parent){throw domain_error ("invalid operation");}return parent;}//找爷爷RBTree* RBTree::GrandParent(){if (0 == parent || 0 == parent->Parent()){throw domain_error ("invalid operation");}return parent->Parent();}//找舅舅RBTree* RBTree::Uncle(){ if (parent == GrandParent()->left) return (dynamic_cast<RBTree*> (GrandParent()->right)); else return (dynamic_cast<RBTree*> (GrandParent()->left));}//找兄弟RBTree* RBTree::Sibling(){ if (this == this->parent->left) return dynamic_cast<RBTree*> (this->parent->right); else return dynamic_cast<RBTree*> (this->parent->left);}void RBTree::AttachKey( Object& object, RBTree& prt ){ if (!IsEmpty ()) throw domain_error ("invalid operation"); key = &object; left = new RBTree (); right = new RBTree ();color = RED; parent = &prt;RBTree* tmpLeft = dynamic_cast<RBTree*> (left);RBTree* tmpRight = dynamic_cast<RBTree*> (right);tmpLeft->parent = this;tmpRight->parent = this; }void RBTree::Insert( Object& object,RBTree* prt ){ if (IsEmpty ()) { AttachKey (object, *prt); insert_case1(*this); }else{ int const diff = object.Compare (*key); if (diff == 0) { throw invalid_argument ("duplicate key"); } if (diff < 0) { BST& bstTmp = Left();RBTree& rbTmp = dynamic_cast<RBTree&> (bstTmp); rbTmp.Insert (object, this); } else { BST& bstTmp = Right(); RBTree& rbTmp = dynamic_cast<RBTree&> (bstTmp); rbTmp.Insert (object, this); } }}void RBTree::Withdraw( Object& object ){ if (IsEmpty ()) { throw invalid_argument ("object not found"); } int const diff = object.Compare (*key); if (diff == 0) {if (!Left ().IsEmpty ()){ RBTree* tmp = dynamic_cast<RBTree*> (Left().FindMaxTree()); //Object& max = *(tmp->key); key = tmp->key; Left ().Withdraw (*(tmp->key));}else if (!Right ().IsEmpty ()){ RBTree* tmp = dynamic_cast<RBTree*> (Right().FindMinTree()); //Object& min = *(tmp->key); key = tmp->key; Right ().Withdraw (*(tmp->key));}else{ delete this->left; delete this->right; key = 0; left = 0; right = 0; if (this->parent->color == BLACK) {if (color == RED){color = BLACK;return;}else{DeleteBalance(this->parent);} }} } else if (diff < 0) { RBTree & tmpleft = dynamic_cast<RBTree &> (Left ()); tmpleft.Withdraw (object); } else { RBTree & tmpright = dynamic_cast<RBTree &> (Right ()); tmpright.Withdraw (object); }}void RBTree::DeleteBalance(RBTree *n ){ delete_case1(*n);}
因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的属性需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。
代码有些复杂,我们一段一段来看。先来看插入,新节点N位于树的根上,没有父节点。在这种情形下,我们把它重绘为黑色以满足性质。因为它在每个路径上对黑节点数目增加一,性质符合:
void RBTree::insert_case1( RBTree &n ){if (n.parent == 0) n.color = BLACK; else insert_case2(n);}
情形2:新节点的父节点P是黑色,所以性质4没有失效(新节点是红色的)。在这种情形下,树仍是有效的。性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质:
void RBTree::insert_case2( RBTree &n ){ if (n.parent->color == BLACK) return; /* 树仍旧有效 */ else insert_case3(n);}
情形3:如果父节点P和叔父节点U二者都是红色,(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里下图仅显示N做为P左子的情形)
则我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质5)。现在我们的新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情况的检查):
void RBTree::insert_case3( RBTree& n ){ if ( n.parent->parent == 0) { return; } if (!(n.Uncle()->IsEmpty()) && (n.Uncle())->color == RED) { (n.parent)->color = BLACK; (n.Uncle())->color = BLACK; (n.GrandParent())->color = RED; insert_case1(*n.GrandParent()); } else{ insert_case4(n); }}
情形4:父节点P是红色而叔父节点U是黑色,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。
在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色;接着,我们按情形5处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效:
void RBTree::insert_case4( RBTree& n ){ if (&n == dynamic_cast<RBTree* > ((n.parent)->right) && (n.parent) == (n.GrandParent())->left) { (n.parent)->RotateLeft(); } else if (&n == dynamic_cast<RBTree* >((n.parent)->left) && n.parent == (n.GrandParent())->right) { (n.parent)->RotateRight(); } insert_case5(n);}
这里顺便解释一下左旋和右旋:
void RBTree::RotateLeft(){ if (IsEmpty ()) throw domain_error ("invalid rotation"); RBTree& tmpk1 = dynamic_cast<RBTree&> (this->Left()); RBTree* tmp1 = &tmpk1; RBTree& tmpk2 = dynamic_cast<RBTree&> (this->Right()); RBTree* tmp2 = &tmpk2; RBTree& tmpk3 = dynamic_cast<RBTree&> (this->Right().Left()); RBTree* tmp3 = &tmpk3; RBTree& tmpk4 = dynamic_cast<RBTree&> (this->Right().Right()); RBTree* tmp4 = &tmpk4; this->right = tmp4; this->left = tmp2; tmp2->parent = this; tmp4->parent = this; tmp2->left = tmp1; tmp2->right = tmp3; tmp1->parent = tmp2; tmp3->parent = tmp2; Object* tmpkey = this->key; this->key = tmp2->key; tmp2->key = tmpkey; NODECOLOR c = this->color; this->color = tmp2->color; tmp2->color = c;}void RBTree::RotateRight(){ if (IsEmpty ()) throw domain_error ("invalid rotation"); RBTree& tmpk1 = dynamic_cast<RBTree&> (this->Left()); RBTree* tmp1 = &tmpk1; RBTree& tmpk2 = dynamic_cast<RBTree&> (this->Left().Left()); RBTree* tmp2 = &tmpk2; RBTree& tmpk3 = dynamic_cast<RBTree&> (this->Left().Right()); RBTree* tmp3 = &tmpk3; RBTree& tmpk4 = dynamic_cast<RBTree&> (this->Right()); RBTree* tmp4 = &tmpk4; this->right = tmp1; this->left = tmp2; tmp1->parent = this; tmp2->parent = this; tmp1->left = tmp3; tmp1->right = tmp4; tmp3->parent = tmp1; tmp4->parent = tmp1; Object* tmpkey = this->key; this->key = tmp1->key; tmp1->key = tmpkey; NODECOLOR c = this->color; this->color = tmp1->color; tmp1->color = c;}
情形5:父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。
在这种情形下,我们进行针对祖父节点G的一次右旋转;在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色 (如果 P和 G都是红色就违反了性质4,所以 G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5[4]也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点:
void RBTree::insert_case5( RBTree& n ){ n.parent->color = BLACK; (n.GrandParent())->color = RED; if (&n == dynamic_cast<RBTree* > ((n.parent)->left) && n.parent == (n.GrandParent())->left) { (n.GrandParent())->RotateRight(); } else { /* Here, n == n->parent->right && n->parent == grandparent(n)->right */ (n.GrandParent())->RotateLeft(); }}
如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题(为了表述方便,这里所指的儿子,为非叶子节点的儿子)。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中(如在这里所展示的那样)。我们接着删除我们从中复制出值的那个节点,它必定有少于两个非叶子的儿子。因为只是复制了一个值而不违反任何属性,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。
需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N,称呼它的兄弟(它父亲的另一个儿子)为S。
如果 N 和它初始的父亲是黑色,则删除它的父亲导致通过 N 的路径都比不通过它的路径少了一个黑色节点。因为这违反了属性 4,树需要被重新平衡。有几种情况需要考虑:
情况 1: N是新的根。在这种情况下,我们就做完了。我们从所有路径去除了一个黑色节点,而新根是黑色的,所以属性都保持着。
void RBTree::delete_case1( RBTree& n ){ if (n.parent == 0) n.color = BLACK; else delete_case2(n);}
情况 2:S是红色。在这种情况下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父。
我们接着对调 N的父亲和祖父的颜色。尽管所有的路径仍然有相同数目的黑色节点,现在 N有了一个黑色的兄弟和一个红色的父亲,所以我们可以接下去按 4、5或6情况来处理。(它的新兄弟是黑色因为它是红色S的一个儿子。)
void RBTree::delete_case2( RBTree& n ){ RBTree *s = n.Sibling(); if (s->color == RED) { n.parent->color = RED; s->color = BLACK; if (&n == n.parent->left) (n.parent)->RotateLeft(); else (n.parent)->RotateRight(); } delete_case3(n);}
情况 3:N的父亲、S和 S的儿子都是黑色的。
在这种情况下,我们简单的重绘 S为红色。结果是通过S的所有路径,它们就是以前不通过 N的那些路径,都少了一个黑色节点。因为删除 N 的初始的父亲使通过 N的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过 P的所有路径现在比不通过 P的路径少了一个黑色节点,所以仍然违反属性4。要修正这个问题,我们要从情况 1开始,在 P上做重新平衡处理。
void RBTree::delete_case3( RBTree& n ){ RBTree *s = n.Sibling(); RBTree *tmpSleft = dynamic_cast<RBTree *> (s->left); RBTree *tmpSright = dynamic_cast<RBTree *> (s->right); if ((n.parent->color == BLACK) && (s->color == BLACK) && (tmpSleft->color == BLACK) && (tmpSright->color == BLACK)) { s->color = RED; delete_case1(*n.parent); } else delete_case4(n);}
情况 4:S和 S的儿子都是黑色,但是 N的父亲是红色。
在这种情况下,我们简单的交换 N的兄弟和父亲的颜色。这不影响不通过 N的路径的黑色节点的数目,但是它在通过 N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。
void RBTree::delete_case4( RBTree& n ){ RBTree *s = n.Sibling(); RBTree *tmpSleft = dynamic_cast<RBTree *> (s->left); RBTree *tmpSright = dynamic_cast<RBTree *> (s->right); if ((n.parent->color == RED) && (s->color == BLACK) && (tmpSleft->color == BLACK) && (tmpSright->color == BLACK)) { s->color = RED; n.parent->color = BLACK; } else delete_case5(n);}
情况 5: S是黑色,S的左儿子是红色,S的右儿子是黑色,而 N是它父亲的左儿子。
在这种情况下我们在 S上做右旋转,这样 S的左儿子成为 S的父亲和 N的新兄弟。我们接着交换 S和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在 N有了一个右儿子是红色的黑色兄弟,所以我们进入了情况 6。N和它的父亲都不受这个变换的影响。
void RBTree::delete_case5( RBTree& n ){ RBTree *s = n.Sibling(); RBTree *tmpSleft = dynamic_cast<RBTree *> (s->left); RBTree *tmpSright = dynamic_cast<RBTree *> (s->right); if (s->color == BLACK) { if ((&n == n.parent->left) && (tmpSright->color == BLACK) && (tmpSleft->color == RED)) { s->color = RED; tmpSleft->color = BLACK; s->RotateRight();; } else if ((&n == n.parent->right) && (tmpSleft->color == BLACK) && (tmpSright->color == RED)) { s->color = RED; tmpSright->color = BLACK; s->RotateLeft(); } } delete_case6(n);}
情况 6:S是黑色,S的右儿子是红色,而 N是它父亲的左儿子。在这种情况下我们在 N的父亲上做左旋转,这样 S成为 N的父亲和 S的右儿子的父亲。我们接着交换 N的父亲和 S的颜色,并使 S的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以属性 3没有被违反。但是,N现在增加了一个黑色祖先:要么 N的父亲变成黑色,要么它是黑色而 S被增加为一个黑色祖父。所以,通过 N的路径都增加了一个黑色节点。
此时,如果一个路径不通过 N,则有两种可能性:
(1)它通过 N的新兄弟。那么它以前和现在都必定通过 S和 N的父亲,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。
(2)它通过 N的新叔父,S的右儿子。那么它以前通过 S、S的父亲和 S的右儿子,但是现在只通过 S,它被假定为它以前的父亲的颜色,和 S的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。
在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了属性 4。
在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色:
void RBTree::delete_case6( RBTree& n ){ RBTree *s = n.Sibling(); RBTree *tmpSleft = dynamic_cast<RBTree *> (s->left); RBTree *tmpSright = dynamic_cast<RBTree *> (s->right); s->color = n.parent->color; n.parent->color = BLACK; if (&n == n.parent->left) { tmpSright->color = BLACK; n.parent->RotateLeft(); } else { tmpSleft->color = BLACK; n.parent->RotateRight(); }}
- 树及树的算法(4) —— 红黑树
- 无向带权图的最小生成树算法——Prim及Kruskal算法思路
- 无向带权图的最小生成树算法——Prim及Kruskal算法思路
- 树及树的算法(5) —— B树(上)
- 树及树的算法(1) —— 二叉树
- 树及树的算法(2) —— 二叉查找树
- 树及树的算法(3) —— 平衡二叉树
- 【算法】B+树的研读及实现(1)
- 【算法】R树的研究及实现(1)
- Prim算法的实现及应用( 最小生成树)
- 最小生成树(MST)的性质及算法 [转】
- 【基础算法】(11)树的概念及相关算法(一)
- 二叉树(一)——二叉树的构造及三种遍历算法的递归实现(java版)
- 【老鸟学算法】二元查找树转变成排序的双向链表——算法思想及java实现
- 数据结构与算法专题之树——二叉树的遍历及应用
- 数据结构之自建算法库——二叉树的链式存储及基本运算
- 数据结构与算法之——二叉树的创建及遍历
- 数据结构之自建算法库——二叉树的链式存储及基本运算
- 2012年5月31日23:16:25
- span 超过一定的长度显示...
- 教程:实现Android的不同精度的定位(基于网络和GPS)
- 【观点】我跳槽是因为他们的显示器更大
- nginx学习笔记0
- 树及树的算法(4) —— 红黑树
- PS 命令详解
- C++字符串与数值——安全的类型转换扩展
- oracle的多表insert操作
- iphone开发一些好的网站推荐
- 网页自动刷新
- java程序利用接口实现发短信功能
- ACMer Blog
- ubuntu10.10编译Android源码