算法导论(6) 红黑树
来源:互联网 发布:淘宝发红包的钱在哪里 编辑:程序博客网 时间:2024/06/06 01:54
1.红黑树
许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。一颗红黑树是满足下面红黑性质的二叉搜索树(当一个结点没有子结点或者父结点时,指针设为空,并且将空指针视为叶结点(外部结点),为黑色):
(1)每个结点或是红色的,或是黑色的。
(2)根结点是黑色的。
(3)每个叶结点是黑色的。
(4)如果一个结点是红色的,则它的两个子结点都是黑色的。
(5)对每个结点,从该结点到其所有后代叶结点的简单路劲上,均包含相同数目的黑色结点。这条简单路径上的黑色结点数目成为黑高,红黑树的黑高为根结点的黑高。
红黑树的结点包含5个属性:color, key, left, right, p。一颗有n个内部结点的红黑树的高度至多为2lg(n+1)。因此,SEARCH, MINIMUM, MAXIMUM, SUCCESSOR, PREDECESSOR(与二叉搜索树相同)可在红黑树上O(lgn)/ O(h)时间内执行。
INSERT与DELETE操作也在O(lgn)时间内执行,但可能会破坏掉红黑树的性质,因此需要旋转(ROTATE)操作进行恢复,包括左旋和右旋。对一个结点左旋时,该结点右孩子不能为叶结点;对一个结点右旋时,左孩子不为叶结点。
程序如下:
class Node{private: int key;//值public: string color; Node *left;//左子树 Node *right;//右子树 Node *parent;//父结点 int getkey(); void setkey(int k); Node(int k = 0); bool operator == (Node &z); Node& operator = (Node &z);};//红黑树class RBTree{public: Node *root; RBTree(){ root = NULL; } Node* leftrotate(Node *root, Node *x); Node* rightrotate(Node *root, Node *x); Node* treeinsert(Node *root, Node *z);//插入结点 Node* insertfixup(Node *root, Node *z); Node* treesearch(Node *root, int k);//查询结点 void treewalk(Node *root);//遍历 Node* treemin(Node *root);//寻找结点root下最小值结点 Node* treemax(Node *root);//寻找结点root下最大值结点 Node* treesuccessor(Node *x);//寻找结点x的后继结点 Node* treeprodecessor(Node *x);//寻找结点x的前驱结点 Node* treetransplant(Node *root, Node *u, Node *v);//在根结点root下移动子树v到u Node* treedelete(Node *root, Node *z);//删除结点 Node* deletefixup(Node *root, Node *z);};Node::Node(int k):key(k){ color = "red"; left = NULL; right = NULL; parent = NULL;}bool Node::operator == (Node &z){ if ((key == z.key)&&(left == z.left)&&(right == z.right)&&(parent == z.parent)&&(color==z.color)) { return true; } else{ return false; }}Node& Node::operator = (Node &z){ delete left; delete right; delete parent; left = z.left; right = z.right; parent = z.parent; key = z.key; color = z.color; return *this;}int Node::getkey(){ return key;}void Node::setkey(int k){ key = k;}Node* RBTree::leftrotate(Node *root, Node *x){ Node *y = x->right; //x的右孩子不为叶结点 if (y != NULL){ x->right = y->left; if (y->left != NULL){ y->left->parent = x; } y->parent = x->parent; if (x->parent == NULL){ root = y; } else if (x == x->parent->left){ x->parent->left = y; } else{ x->parent->right = y; } y->left = x; x->parent = y; } return root;}Node* RBTree::rightrotate(Node *root, Node *x){ Node *y = x->left; if (y != NULL){ x->left = y->right; if (y->right != NULL){ y->right->parent = x; } y->parent = x->parent; if (x->parent == NULL){ root = y; } else if (x == x->parent->left){ x->parent->left = y; } else{ x->parent->right = y; } y->right = x; x->parent = y; } return root;}//查询、最大值、最小值、前驱、后继函数均可以在O(h)时间内完成,h为树的高度Node* RBTree::treesearch(Node *root, int k)//查询结点{ //与二叉搜索树相同}void RBTree::treewalk(Node *root)//中序遍历{ if (root != NULL) { treewalk(root->left); cout << root->getkey() << ":"<<root->color <<" "; treewalk(root->right); }}Node* RBTree::treemin(Node *root)//寻找结点root下最小值结点{ //与二叉搜索树相同}Node* RBTree::treemax(Node *root)//寻找结点root下最大值结点{ //与二叉搜索树相同}Node* RBTree::treesuccessor(Node *x)//寻找结点x的后继结点{ Node *y; //如果右孩子不空,则右子树中的最小值即为后继 if (x->right != NULL){ return treemin(x->right); } y = x->parent; //如果父结点也为空,无后继结点 //父结点存在时,后继结点为它的最底层祖先,并且后继结点的左结点也是它的祖先 //因此循环的终止条件为x == y->right,当x是一个左孩子时终止,x的父结点为后继结点 while (y != NULL && x == y->right) { x = y; y = y->parent; } if (y != NULL) { return y; } return 0;}Node* RBTree::treeprodecessor(Node *x)//寻找结点x的前驱结点{ Node *y; //如果左孩子存在,前驱结点为左子树中最大值 if (x->left != NULL){ return treemax(x->left); } y = x->parent; //与后继结点类似,当x为右孩子时终止,x父结点为前驱结点 while (y != NULL && x == y->left) { x = y; y = y->parent; } if (y != NULL) { return y; } return 0;}Node* RBTree::insertfixup(Node *root, Node *z){ Node *y=new Node(); //保证性质4,只有执行情况1时,循环会继续进行,执行2和3时,执行一次后循环就停止 while (z->parent != NULL&&z->parent->parent != NULL&&z->parent->color == "red"){ if (z->parent == z->parent->parent->left){ y = z->parent->parent->right; if (y != NULL&&y->color == "red"){ //情况1:z的叔结点y是红色,无论z是左孩子还是右孩子,改变父结点、叔结点以及祖父结点的颜色来保证性质5,将z上移两层,重新进入循环 z->parent->color = "black"; y->color = "black"; z->parent->parent->color = "red"; z = z->parent->parent; } else{ //情况2:叔结点y是黑色,z是一个右孩子,将z指向父结点,左旋,将情况2转为情况3 if (z == z->parent->right) { z = z->parent; root=leftrotate(root, z); } //情况3:叔结点为黑色,z是一个左孩子,改变z的父节点以及祖父结点的颜色,然后右旋 z->parent->color = "black"; z->parent->parent->color = "red"; root = rightrotate(root, z->parent->parent); } } else{ //与之前过程相同,但左右对换 y = z->parent->parent->left; if (y != NULL&&y->color == "red"){ //情况1 z->parent->color = "black"; y->color = "black"; z->parent->parent->color = "red"; z = z->parent->parent; } else{ //情况2 if (z == z->parent->left) { z = z->parent; root = rightrotate(root, z); } //情况3 z->parent->color = "black"; z->parent->parent->color = "red"; root = leftrotate(root, z->parent->parent); } } } //保证性质2 root->color = "black"; return root;}Node* RBTree::treeinsert(Node *root, Node *z)//插入结点{ Node *x, *y = NULL; x = root; //向下寻找位置 while (x != NULL) { y = x; if (z->getkey() < x->getkey()) { x = x->left; } else{ x = x->right; } } z->parent = y; //判断树是否为空 if (y == NULL) { root = z; } else if (z->getkey() < y->getkey()) { y->left = z; } else{ y->right = z; } z->left = NULL; z->right = NULL; //插入的是红色结点,插入后在最底层,这样至多有一条性质被破坏,性质2或是性质4 z->color = "red"; root = insertfixup(root, z); return root;}Node* RBTree::deletefixup(Node *root, Node *x){ //当y为黑色时,首先破坏的性质就是,原来包含y结点的路径上黑高少了1,其次就是x移动到y原来位置上后,可能出现x与x.p均为红色的情况 //书中所说y为根结点,y的红色孩子成为新的根节点,感觉这种情况不存在 //因为y的孩子替换z时,最后会把z的颜色赋值给y的颜色,假如入要删除的是根结点,这一步骤自然会保证根结点颜色不变 //假如根结点成为要删除结点的后继,要删除节点一定在根结点左子树中,而这时删除节点一定是左子树中的最大值,一定没有右孩子存在 //这时,直接拿左孩子替换要删除结点就行,不会存在根结点颜色变化的情况,最多是在修正过程中改变了根结点的颜色 //处理方案是假设x有额外一层黑色,只要将x原来的颜色去掉或者传递给其他红色的结点即可 Node *w; //如果x是原来是红色的,直接赋值为黑色即可,因此循环条件是x为黑色 //当x为根结点时,直接赋值为黑色 while (!(*x == *root) && x->color == "black"){ //x为左孩子时,为右孩子的时候类似 if (x == x->parent->left){ w = x->parent->right; //情况1:x的兄弟结点是红色的,经过变色以及旋转将其改变为新的结点并转换为黑色,将情况1转为情况2,3,4 if (w->color == "red"){ w->color = "black"; x->parent->color = "red"; root = leftrotate(root, x->parent); w = x->parent->right; } //情况2:x的兄弟结点w是黑色的,并且w两个子结点都是黑色的 //x和w均为黑色,将x上额外的黑色以及w的黑色都去掉,加在他们的父节点上,将w变为红色即为去掉黑色,然后对新的x重新进行循环 if ( w->left->color == "black"&&w->right->color == "black"){ w->color = "red"; x = x->parent; } else{ //情况3:x的兄弟结点w是黑色,w左孩子为红色,右孩子为黑色,交换w与w左孩子的颜色,右旋,转为情况4 if ( w->right->color == "black"){ w->left->color = "black"; w->color = "red"; root = rightrotate(root, w); w = x->parent->right; } //情况4:x的兄弟结点是黑色,w右孩子为红色,将其变为黑色,相当于给x.p右子树多加一个黑色 //右旋是为了将w的黑色转到x.p右子树,右旋后,w变为原来的x.p,保留原来x.p的颜色,而将原来的x.p变为黑色,转到了的新的x.p的左子树上,也即那一层额外的黑色 w->color = x->parent->color; x->parent->color = "black"; w->right->color = "black"; root = leftrotate(root, x->parent); //将x设置为根结点,退出循环 x = root; } } else{ w = x->parent->left; if (w->color == "red"){ w->color = "black"; x->parent->color = "red"; root = rightrotate(root, x->parent); w = x->parent->left; } if (w->right->color == "black"&&w->left->color == "black"){ w->color = "red"; x = x->parent; } else{ if (w->left->color == "black"){ w->right->color = "black"; w->color = "red"; root = leftrotate(root, w); w = x->parent->left; } w->color = x->parent->color; x->parent->color = "black"; w->left->color = "black"; root = rightrotate(root, x->parent); x = root; } } } x->color = "black"; return root;}Node* RBTree::treetransplant(Node *root, Node *u, Node *v)//在根结点root下移动子树v到u{ //u为根结点 if (u->parent == NULL) { root = v; } else if (u == u->parent->left) { u->parent->left = v; } else{ u->parent->right = v; } //书中提到为空的时候也要赋值,但没有设置哨兵,只是将NULL当做哨兵来理解,实际上还是要判断 if (v != NULL){ v->parent = u->parent; } return root;}Node* RBTree::treedelete(Node *root, Node *z)//删除结点{ //y用来指向要删除的结点以及将要移动到删除位置的结点 Node *y=z; //x移动到了y的原始位置,记录该点踪迹,因为它可能破坏树的性质 Node *x; string yOriginalColor = y->color; if (z->left == NULL){ x = z->right; //如果左孩子为空,利用右孩子替换,右孩子为空时,相当于直接删除 root = treetransplant(root, z, z->right); } else if (z->right == NULL){ x = z->left; //执行此步时,一定只有左孩子,拿左孩子替换即可 root = treetransplant(root, z, z->left); } else{ //左右孩子都存在,首先需要寻找它的后继结点 y= treemin(z->right); yOriginalColor = y->color; //此时y没有左孩子 x = y->right; if (!(y == z->right)){ //如果后继结点不是它的右孩子,后继结点没有左孩子,否则不是后继结点 //先拿后继结点右孩子替换后继结点,再拿后继结点替换要删除的结点 root = treetransplant(root, y, y->right); //将z的右孩子换到y上,if外的语句把z的左孩子换到y上 y->right = z->right; y->right->parent = y; } //如果后继结点就是它的右孩子,并且这个右孩子没有左孩子,否则不可能是后继结点,因此直接拿后继结点替换 treetransplant(root, z, y); y->left = z->left; y->left->parent = y; y->color = z->color; //书中设置了x->parent = y是防止x为空时,x.p不存在,进入到修正程序中会有错误,但x为空时,不能出现x->parent //x为空也就是哨兵时依旧可能破坏了红黑树的性质,也应该进行进行恢复,后来才发现的,但目前程序由于时间关系中没有写 //如果y原来的颜色是黑色,需要重新维护红黑树的性质,理由如下: //当y为红色时,删除或者移动,首先,树的黑高没有发生变化,y也不是树的根结点,根结点没有变化 //对于前两种情况,z无孩子或者只有一个孩子的时候,y=z,无孩子,红色删掉不影响,有一个孩子时候,y也不可能是红色,因为黑高不对 //对于第三种情况中,y为z的后继,y为z的右孩子时,y也不可能为红色,因为y此时没有左孩子,黑高就不对 //y不是z的右孩子时,y为红色,没有左孩子,其右孩子为黑色,替换它后不影响树的性质,而y移动到z,颜色变为z的颜色,也不会影响性质 if (x != NULL&&yOriginalColor == "black"){ root = deletefixup(root, x); } } return root;}void main(){ RBTree rbtree; rbtree.root = new Node(14); rbtree.root->color = "red"; Node z[15] = { 5, 49, 2, 18, 15, 30, 17, 56, 46, 10, 12, 44,35, 37, 19 }; for (int i = 0; i < 15; i++) { rbtree.root = rbtree.treeinsert(rbtree.root, &z[i]); } cout << "中序遍历二叉树:" << endl; rbtree.treewalk(rbtree.root); cout << endl; Node *s; s = rbtree.treesearch(rbtree.root, 2); if (s == NULL){ cout << "无" << endl; } else{ cout << s->getkey() << endl; } s=rbtree.treeprodecessor(&z[2]); if (s == NULL){ cout << "无" << endl; } else{ cout << s->getkey() << endl; } s = rbtree.treesuccessor(&z[6]); if (s == NULL){ cout << "无" << endl; } else{ cout << s->getkey() << endl; } cout << "最大值:" << endl; cout << rbtree.treemax(rbtree.root)->getkey() << endl; cout << "最小值:" << endl; cout << rbtree.treemin(rbtree.root)->getkey() << endl; rbtree.root = rbtree.treedelete(rbtree.root, &z[12]); cout << "中序遍历二叉树:" << endl; rbtree.treewalk(rbtree.root); cout << endl;}
需要注意的就是在删除操作的修复程序中,x为空的情况,程序中没有处理,但应该进行处理,主函数中的数据示例就是x出现了为空的情况,最后黑高不正确,有时间再研究。
2.顺序统计树
在每个结点上添加附加信息的一棵红黑树。除包括红黑树的五个属性外还包括另一个属性size,表示以该结点为根的子树(包含自身)的结点数:
x.size=x.left.size+x.right.size+1
一个元素的秩定义为在中序遍历树时输出的位置。中序遍历其实就是从小到大排序,但中间可能出现key值相同的元素,秩不相同。
(1)查找以x为根结点的树中具有给定秩的元素:
OS-SELECT(x,i) r=x.left.size+1 //计算x结点的秩 if i==r return x elseif i<r //在x左子树中 return OS-SELECT(x.left,i) else //在x右子树中 return OS-SELECT(x.right,i-r)
(2)确定一个元素的秩:
OS-RANK(T,x) r=x.left.size+1 //r为以y为根结点的子树中x结点的秩 y=x while y!=T.root //当y是其父结点的右孩子时,x的秩要加上y兄弟结点的size再加1,如果y是其父结点的左孩子,则在该子树中已无比它key值更小的结点 if y==y.p.right r=r+y.p.left.size+1 y=y.p return r
为了能准确的运用上面的函数,必须要再红黑树的基础操作中对size属性进行维护。
对于插入操作,新增加的结点size为1,从根到新增结点的路径上的每一个结点的size+1,对于旋转操作,会使两个结点的size属性失效,x及其孩子结点y,旋转操作的维护是利用下面的代码:
y.size=x.size
x.size=x.left.size+x.right.size+1
对于删除操作,遍历y结点(从树中的原始位置开始)到根结点的简单路径,路径上每个结点的size减1。
3.扩张数据结构
(1)选择一种基础数据结构
(2)确定基础数据结构中要维护的附加信息。
(3)检验基础数据结构上的基础修改操作能否维护附加信息。
(4)设计一些新操作。
- 算法导论(6) 红黑树
- 红黑树(算法导论)
- 算法导论(6)
- 算法导论13(红黑树)
- 算法导论读书笔记(6)
- 算法导论学习笔记(6)——红黑树
- 算法导论学习笔记(九):红黑树
- 红黑树的插入(算法导论)
- 红黑树的删除(算法导论)
- 算法导论——(3)红黑树
- 算法导论学习笔记(三)红黑树
- 算法导论读书笔记(13)红黑树
- 算法导论 ch13 红黑树
- 算法导论 红黑树
- 算法导论 ch13 红黑树
- 算法导论 红黑树基础知识
- 红黑树(算法导论13)
- 算法导论红黑树
- hdu 5726 GCD(线段树+预处理)
- JSP属性范围
- 从非正规格式的输入数得到正规输入格式
- Sort Algorithm-->Bubble Sort
- 【c++】STL里的priority_queue用法总结
- 算法导论(6) 红黑树
- HDU-2012
- 百练2815:城堡问题
- OpenCv之图像腐蚀
- 关于SPFA算法
- Integer与int的种种比较
- 安卓学习笔记Ubuntu 16.04 LTS中安卓开发环境的搭建步骤
- Codeforces Round #364 (Div. 2) D As Fast As Possible(数学)
- 1445. 回家【推荐】 的题解