/*红黑树:一种二叉查找树,某种程度上也是一种平衡树,其中平衡树还有avl树。 红黑树:对于红黑树的而言,首先是一个二叉查找树,此外每一个节点包含的还有一项技术指标就是 每个节点还有一个表示该节点颜色的项目,节点要么是红色要么是黑色,为了限制红黑树的平衡型,通过 使各个分支上的红黑的着色方式达到一种近似的平衡,所以红黑树算是一种平衡树,类似的平衡树还有:avl树,treap树; 红黑树的特性:(1)每一个节点颜色:红或者黑; (2)根节点是黑色的; (3)一个节点为红,子节点为黑; (4)每一个叶子节点(算法导论里面的意思为非含有键值节点)为黑; (5)从某一节点起到叶子节点为止,各个分支的黑色的节点个数一样。(黑色的个数也叫黑高度,一个树的黑高度就是从树的根节点开始到叶子节点。 红黑树的基本动态操作是O(lgn) */ #include <iostream> #define RED 1 #define BLACK 0 using namespace std; struct node { int key; node* p; node* left; node* right; bool color; node(){ key=0; p=NULL; left=NULL; right=NULL; color=RED; } node(int k):key(k){ p=NULL; left=NULL; right=NULL; color=RED; } node& operator=(node& other) { if(this!=&other) { this->key=other.key; this->p=other.p; this->color=other.color; this->left=other.left; this->right=other.right; } return *this; } }; struct rbtree { node* root;//根 node* nil;//哨兵 rbtree() { int k; cin>>k; root=new node(k); root->color=BLACK; nil=new node(100000); nil->color=BLACK; root->p=nil;//一开始根节点就指向该值 root->left=nil;//最初的时候如此的进行设计。 root->right=nil; } void letf_rotate(node*);//o(1) void right_rotate(node*);//o(1) void rb_insert_fixup(node*); void rb_insert(node*);//o(lgn) void rb_delete_fixup(node*); node* rb_delete(node*);//o(lgn) node* rb_successor(node*);//辅助 node* rb_min(node*);//辅助 }; void rbtree::letf_rotate(node* px) { node* py=px->right; if(py!=nil) { px->right=py->left; if(py->left!=nil) py->left->p=px; py->p=px->p; if(px==nil) root=py; if(px==px->p->left) px->p->left=py; else px->p->right=py; py->left=px; px->p=py; } else { cerr<<"left of px is null "<<endl; return ;//一般情况下函数返回值为void 可以如此; } cout<<"finished! "<<endl; } void rbtree::right_rotate(node* px)//13.2-1 { node* py=px->left; if(py!=nil) { px->left=py->right; if(py->right!=nil) py->right->p=px; py->p=px->p; if(px->p==nil) root=py; if(px==px->p->left) px->p->left=py; else px->p->right=py; py->right=px; px->p=py; }else { cerr<<"right of px is nil "<<endl; return ; } cout<<"finishede!"<<endl; } /************rbtree_insert()************************/ void rbtree::rb_insert_fixup(node* pz) { node* py=new node(); while(pz->p->color==RED)//对于红黑树,插入一个点为红,只要保证最后的树是满足红黑树的五个性质即可,插入的是红点,黑高度不影响,那么插入 {//红点,会造成的影响是:如果插入的点成为根节点的话;以及插入节点的父节点为红。这里以插入节点的父节点为红做判断。 if(pz->p==pz->p->p->left) { py=pz->p->p->right;//py为pz的叔叔 if(py->color==RED)//情况1:叔叔为红的时候 { pz->p->color=BLACK; py->color=BLACK; pz->p->p->color=RED;//修改颜色使父辈变黑,爷爷变红,这样节点黑高度不受影响,原因是pz为红,pz父辈为红,那么对应必定有祖父辈,插入前的rbtree为 //正常的,父辈不会为根,那么存在祖父辈,如果祖父辈为红,父辈怎么会为红呢?祖父辈必定为黑以保证插入前的rbtree是正常。 pz=pz->p->p; }else { if(pz==pz->p->right)//情况2:叔叔为黑,且pz是右孩子 { pz=pz->p; letf_rotate(pz); } pz->p->color=BLACK;//情况3:叔叔为黑,pz是左孩子 pz->p->p->color=RED; right_rotate(pz->p->p); } }else if(pz->p==pz->p->p->right) { py=pz->p->p->left;//叔叔 if(py->color==RED) { pz->p->color=BLACK; py->color=BLACK; pz->p->p->color=RED; pz=pz->p->p; }else { if(pz==pz->p->left) { pz=pz->p; right_rotate(pz); } pz->p->color=BLACK; pz->p->p->color=RED; letf_rotate(pz->p->p); } } } root->color=BLACK;//这里的做法,可以避免插入的节点成为根节点,黑高度还是不受影响,只是大了一个 // delete py; } /** 插入算法理解:插入的pz为红,这样可以避免节点黑高度的影响,以pz的父节点为黑与否作为循环的标准判定,这时候只存在一种意外情况即:根节点是pz,pz的父为黑的,那么的话 就会影响rbtree的属性,但是最终结尾的时候我们有一个修改根的颜色可以保证,此外当pz的父节点是红的时候,我们可以根据上面的情况判断存在祖父节点,且为黑,此外的话 pz,pz的父节点为红,这是基本的属性情况,下面分三种也就是算法导论所述的情况,即叔叔节点为红,叔叔节点为黑时候pz为右子树和左子树的三种情况 情况1:为了使红黑树的五个性质满足,从祖父点开始,父辈那层为红,为了避免pz,pz父辈的同色,修改颜色即可,这样的话祖父为红,是唯一的纰漏,相应的向上回溯即可: 情况2:对于这种父辈不同色的情况是存在的,比如叔叔的是一个nil,父为红,接着接nil,为了满足红黑树的五个属性根据上面分析父,祖父颜色还是那样的,为了下一步使黑高度 满足,且pz,pz父颜色不同,进行了所谓的旋转。 时间复杂度:由于一开始的insert()是对一颗二叉树插入一个点,那么一般情况下的话就是树的高度o(lgn),后面的rb_insert_fixup()主要是一个while也是while树的高度 所以时间复杂度是o(lgn) **/ void rbtree::rb_insert(node* pz)//红黑树是二叉查找树,插入节点先图红,可以保证节点黑高度,插入的是类似二叉查找树的插入 { node* py=nil; node* px=root; while(px!=nil) { py=px; if(pz->key>px->key) px=px->right; else px=px->left; } pz->p=py; if(py==nil) root=pz; else if(pz->key<py->key) py->left=pz; else py->right=pz; pz->left=nil; pz->right=nil; pz->color=RED; rb_insert_fixup(pz); // delete py; // delete px; } /****************习题******************************/ //13.3-1 ans:会影响节点黑高度 /*13.3-2 ans: 38(black) 19(red) 41(black) 12(black) 31(black) 8(red) */ /******************rbtree_delete()*******************************/ node* rbtree::rb_min(node* pz) { while (pz->left!=nil) { pz=pz->left; } return pz; } node* rbtree::rb_successor(node* pz)//中序遍历求后继 { node* py=new node(); if(pz->right!=nil) return rb_min(pz->right); py=pz->p; while (py!=nil&&pz==py->right) { pz=py; py=py->p; } return py; } void rbtree::rb_delete_fixup(node* pz) {/* 这个函数用来修改删除一个黑节点之后的子树,因为如果是删除红色,木有影响但是删除的是黑色的话就会有影响比如说黑高度影响,以及删除之后 会造成父子同为红色的可能性以及根节点为红色等可能性,所以需要进行调整,有一个while循环,循环会造成向上回溯,回溯到最终的终结的话就是 要么到达根节点,要么是节点颜色属性是红的(这样为了满足黑高度可以把这个点染黑),根据二叉树的删除来计算的话,删除的那个点的下一个点必 为单个的情况,要么左子树,要么右子树,要么无子树,相应的作为判断的pz点,就可以用其颜色作为while标准,为红会后续的直接涂黑(直接增加黑高度) 为黑的话,就要进行while的操作。 */ node* pw=new node(); while(pz!=root&&pz->color==BLACK)//为根节点,整体黑高度不影响,为红,后面会涂黑。 { if(pz==pz->p->left) { pw=pz->p->right; if(pw->color==RED)//只有是父节点是黑色的情况pw才可能是红色。 { pz->p->color=RED; pw->color=BLACK; letf_rotate(pz->p); pw=pz->p->right; }//情况1 if (pw->left->color==BLACK&&pw->right->color==BLACK) { pw->color=RED;//只用堆pw修改,pz实际上是黑的,去掉一重还是黑,不要改,但是pw要; pz=pz->p;//此操作结束后,pz为红,结束并涂黑,如果pz为黑,还要pz加一重黑 }//情况2:去掉一重黑,让存在双重黑的pz变正常,如果是从情况1来,就是pz为红的了,如果不是的话,pz为黑,按照算法导论所述,pz让存在 //双重黑,主要是保证该树黑高度,从pz节点起的黑高度和没有删某个黑节点一样的,主要是pz的父那个节点开始少一个黑高度。 else { if(pw->right->color==BLACK)//情况3 { pw->left->color=BLACK; pw->color=RED; right_rotate(pw); pw=pz->p->right; }//情况4 pw->color=pz->p->color; pz->p->color=BLACK; pw->right->color=BLACK; letf_rotate(pz->p); pz=root; } } else if(pz==pz->p->right) { pw=pz->p->left;//叔叔 if(pw->color=RED) { pz->p->color=RED; pw->color=BLACK; right_rotate(pz->p); pw=pz->p->left; } if(pw->left->color==BLACK&&pw->right->color==BLACK) { pw->color=RED; pz=pz->p; } else { if(pw->right->color==BLACK) { pw->left->color=BLACK; pw->color=RED; letf_rotate(pw); pw=pz->p->left; } pw->color=pz->p->color; pz->p->color=BLACK; pw->right->color=BLACK; right_rotate(pz->p); pz=root;//只要到达情况4就可以完成了,因为已经添加一个黑高度,所以用此结束循环。 } } } pz->color=BLACK; // delete pw; //上面的四种情况不一定是按照顺序来的,但是最终的解决都是通过情况4进行解决,前面三种情况通过转换最后都会变为情况4,那么相应的 //对于情况4,而言我们本身是需要pz(删除的节点的下一个点),含有2重黑色,但是实际上是要求pz上面一层添加一个黑色,情况4就是如此做 //通过把pw的颜色换pz的父颜色,父色变黑,在左旋,那么造成,pz的上面一层为父(黑),上面一层为pw原来颜色(父变黑前色),pw的右子树由 //红变黑,那么这边对应黑高度不变。无论是那种情况最终都是变为情况四处理的。 } /* 时间复杂度:o(lg(n)) */ node* rbtree::rb_delete(node* pz) { node* py=new node(); node* px=new node(); if (pz->left==nil||pz->right==nil)//pz这节点有单个左或者单个右或者全无,反就是有左有右 py=pz; else py=rb_successor(pz);//如果是有左右子树,就是求后继,下面的操作无论是对于后继还是非后继都是一样的。 if(py->left!=nil) px=py->left; else px=py->right; px->p=py->p; if(py==nil) root=px; else if(py==py->p->left) py->p->left=px; else py->p->right=px; if(py!=pz) pz->key=py->key; if (py->color==BLACK)//若是一个黑的话,会影响黑高度,所以要调整 { rb_delete_fixup(px); } // delete py; // delete px; return py; } // /*********************main_function()************************************/ int main() { rbtree* rb=new rbtree(); node* p1=new node(1); node* p2=new node(2); node* p3=new node(3); // rb->letf_rotate(p1);//一开始运行此处的时候会报错,原因很简单,在上面的py->left等都是空的。所以赋值的时候会出错的。勿怪了 // rb->right_rotate(p2); rb->rb_insert(p1); rb->rb_insert(p2); rb->rb_insert(p3); rb->rb_delete(p2); delete p1; delete p2; delete p3; return 0; } /* stl容器使用rbtree为底层实现的细节解释: */
算法导论十三章复习
最新推荐文章于 2023-12-13 21:11:55 发布