《算法导论》笔记 第13章 总结与思考
【总结】
template <class T> class RBTREE{ public: struct NODE{ NODE *p,*l,*r; T key; COLOR c; NODE() {} }NIL; NODE *nil, *root; NODE* newNode(T k, COLOR cl = RED) { NODE *p = new NODE; p->c = cl; p->p = nil; p->l = nil; p->r = nil; p->key = k; return p; } void deleteNode(NODE *p) { delete p; } void init() { nil = &NIL; nil->c = BLACK; nil->p = nil; nil->l = nil; nil->r = nil; root = nil; } RBTREE () { init(); } /*********************************************/ void leftRotate(NODE *x) { NODE *y = x->r; x->r = y->l; if (y->l != nil) { y->l->p = x; } y->p = x->p; if (x->p == nil) { root = y; } else { if (x == x->p->l) { x->p->l = y; } else { x->p->r = y; } } y->l = x; x->p = y; } void rightRotate(NODE *x) { NODE *y = x->l; x->l = y->r; if (y->r != nil) { y->r->p = x; } y->p = x->p; if (x->p == nil) { root = y; } else { if (x == x->p->l) { x->p->l = y; } else { x->p->r = y; } } y->r = x; x->p = y; } /*********************************************/ void rbInsert(NODE *z) { NODE *y = nil; NODE *x = root; while (x != nil) { y = x; if (z->key < x->key) { x = x->l; } else { x = x->r; } } z->p = y; if (y == nil) { root = z; } else { if (z->key < y->key) { y->l = z; } else { y->r = z; } } z->l = nil; z->r = nil; z->c = RED; rbInsertFixup(z); } void rbInsertFixup(NODE *z) { NODE *y; while (z->p->c == RED) { if (z->p == z->p->p->l) {// z 的父亲是爷爷的左儿子 y = z->p->p->r;// z 的叔叔 y if (y->c == RED) {// case 1:叔叔是红的 z->p->c = BLACK;// 将 z 的父亲与叔叔置为黑 y->c = BLACK; z->p->p->c = RED;// 将 z 的爷爷置为红 z = z->p->p;// 问题上移两层 } else { if (z == z->p->r) {// case 2:z 是右儿子 z = z->p; leftRotate(z);// 左旋,转为 case 3 } z->p->c = BLACK;// case 3:z 是左儿子,对z的爷爷做一次右旋即可完成维护 z->p->p->c = RED; rightRotate(z->p->p); } } else if (z->p == z->p->p->r) {// z 的父亲是爷爷的右儿子 y = z->p->p->l;// z 的叔叔 y if (y->c == RED) {// case 1:叔叔是红的 z->p->c = BLACK;// 将 z 的父亲与叔叔置为黑 y->c = BLACK; z->p->p->c = RED;// 将 z 的爷爷置为红 z = z->p->p;// 问题上移两层 } else { if (z == z->p->l) {// case 2:z 是左儿子 z = z->p; rightRotate(z);// 右旋,转为 case 3 } z->p->c = BLACK; z->p->p->c = RED; leftRotate(z->p->p); } } } root->c = BLACK; } /*********************************************/ NODE* treeMinimum(NODE *rt) { while (rt->l!=nil) rt=rt->l; return rt; } NODE* treeMaximum(NODE *rt) { while (rt->r!=nil) rt=rt->r; return rt; } NODE* treeSuccessor(NODE *rt) { if (rt->r!=nil) return treeMinimum(rt->r); NODE* pt=rt->p; while (pt!=nil && rt==pt->r) { rt=pt; pt=pt->p; } return pt; } NODE* treePredecessor(NODE *rt) { if (rt->l!=nil) return treeMaximum(rt->l); NODE* pt=rt->p; while (pt!=nil && rt==pt->l) { rt=pt; pt=pt->p; } return pt; } /*********************************************/ NODE* rbDelete(NODE *z) { NODE *y, *x; if (z->l == nil || z->r == nil) { y = z; } else { y = treeSuccessor(z); } if (y->l != nil) { x = y->l; } else { x = y->r; } x->p = y->p; if (y->p == nil) { root = x; } else { if (y == y->p->l) { y->p->l = x; } else { y->p->r = x; } } if (y != z) { z->key = y->key; // copy y's satellite data into z } if (y->c == BLACK) { rbDeleteFixup(x); } return y; } void rbDeleteFixup(NODE *x) { NODE *w; while (x != root && x->c == BLACK) { if (x == x->p->l) {// x 为左儿子 w = x->p->r;// w 是 x 的兄弟 if (w->c == RED) {// case 1:w 为红色,必有黑色儿子 w->c = BLACK; x->p->c = RED; leftRotate(x->p); w = x->p->r;// x 的新兄弟必为黑色 } if (w->l->c == BLACK && w->r->c == BLACK) {// case 2:x 的兄弟 w 是黑色的,w 的两个儿子都是黑色 w->c = RED;// 去掉一重黑色 x = x->p;// 以 x 父亲重复 while 循环 } else { if (w->r->c == BLACK) {// case 3:x 的兄弟 w 是黑色的,w 的左儿子是红色的,右儿子是黑色 w->l->c = BLACK;// 交换 w 与左儿子的颜色 w->c = RED; rightRotate(w);// w 右旋 w = x->p->r;// 新兄弟是一个有红色右孩子的黑结点 } w->c = x->p->c;// case 4:x 的兄弟 w 是黑色的,而且 w 的右儿子是红色的 x->p->c = BLACK; w->r->c = BLACK; leftRotate(x->p); x = root; } } else if (x == x->p->r) {// x 为右儿子 w = x->p->l;// w 是 x 的兄弟 if (w->c == RED) {// case 1:w 为红色,必有黑色儿子 w->c = BLACK; x->p->c = RED; rightRotate(x->p); w = x->p->l;// x 的新兄弟必为黑色 } if (w->l->c == BLACK && w->r->c == BLACK) {// case 2:x 的兄弟 w 是黑色的,w 的两个儿子都是黑色 w->c = RED;// 去掉一重黑色 x = x->p;// 以 x 父亲重复 while 循环 } else { if (w->l->c == BLACK) {// case 3:x 的兄弟 w 是黑色的,w 的右儿子是红色的,左儿子是黑色 w->r->c = BLACK;// 交换 w 与右儿子的颜色 w->c = RED; leftRotate(w);// w 左旋 w = x->p->l;// 新兄弟是一个有红色左孩子的黑结点 } w->c = x->p->c;// case 4:x 的兄弟 w 是黑色的,而且 w 的左儿子是红色的 x->p->c = BLACK; w->l->c = BLACK; rightRotate(x->p); x = root; } } } x->c = BLACK; } /*********************************************/ NODE* treeSearch(NODE *rt,T k) { if (rt==nil || k==rt->key) return rt; if (k<rt->key) return treeSearch(rt->l,k); else return treeSearch(rt->r,k); } void remove(T key) { NODE *p = treeSearch(root,key); if (p != nil) p = rbDelete(p); deleteNode(p); } /*********************************************/ void insert(T key) { rbInsert(newNode(key)); } void showAll(NODE *p) { if (p != nil) { std::cout << "key = " << p->key << ", color = " << p->c << std::endl; std::cout << "LEFT:" << std::endl; showAll(p->l); std::cout << "RIGHT:" << std::endl; showAll(p->r); std::cout << "END." << std::endl; } //else { std::cout << " NIL " << endl; } } };
【思考】
13-1 持久动态集合
a) 对一棵一般的持久二叉查找树,为插入一个关键字 k 或删除一个结点 y 的,确定需要改变哪些节点?
需要新增从根到插入或删除的结点的路径上的所有结点。
b) 请写出一个程序PERSISTENT-TREE-INSERT,使得在给定一棵持久树T和一个要插入的关键字k时,它返回将k插入T后的新的持久树T'。
NODE* persistentTreeInsert(NODE *z,int cnt = -1) { if (cnt == -1) cnt = cur; NODE *y = nil; NODE *x = copyNode(root[cnt]); root[++cur] = x; while (x != nil) { y = x; if (z->key < x->key) { x->c[0] = copyNode(x->c[0]); x = x->c[0]; } else { x->c[1] = copyNode(x->c[1]); x = x->c[1]; } } if (y == nil) { root[cur] = z; } else { if (z->key < y->key) { y->c[0] = z; } else { y->c[1] = z; } } z->c[0] = nil; z->c[1] = nil; return root[cur]; }
c) 如果持久二叉查找树T的高度为h,所实现的PERSISTENT-TREE-INSERT的时间和空间要求分别是多少?
O(h) O(h)
d) 假设我们在每个结点中增加一个父亲结点域。这样一来,PERSISTENT-TREE-INSERT需要做一些额外的复制工作。证明在这种情况下,时空要求为Ω(n),其中n为树中结点个数。
每次更新都要复制整棵二叉树,因此时空要求为Ω(n)。
e) 说明如何利用红黑树来保证每次插入或删除的最坏情况运行时间为O(lgn)。
13-2 红黑树上的连接操作
a) 给定一棵红黑树T,其黑高度被存放在域bh[T]中。证明在不需要树中结点的额外存储空间和不增加渐进运行时间的前提下,可以用RB-INSERT和RB-DELETE来维护这个域。并证明当沿T下降时,可对每个被访问的结点在O(1)时间内确定其黑高度。
在fix中向上维护bh[T]即可。
下降时,遇到黑结点则当前bh-1。
b) 假设bh[T1]>=bh[T2]。请描述一个O(lgn)时间的算法,使之能在T1中从黑高度为bh[T2]的结点中选出具有最大关键字的黑结点y。
尽量向右儿子找。
c) 设Ty是以y为根的子树。说明如何在不破坏二叉查找树性质的前提下,在O(1)时间里用Ty∪{x}∪T2来取代Ty。
以x为根,Ty T2 为左右子树。
d) 要保持红黑性质1),3)和性质5),应将x着什么颜色?说明如何能在O(lgn)时间内恢复性质2)、4)。
红色,调用insertFix(x)。
e) 论证b)部分作的假设不失一般性。描述当bh[T1]<=bh[T2]时所出现的对称情况。
同b)。
f) 证明RB-JOIN的运行时间是O(lgn)。
插入O(1),恢复O(lgn)。
13-3 AVL树
a) 证明一棵有n个结点的AVL树其高度为O(lgn)。
O(lg2n)
b) 描述一个程序BALANCE(x),输入一棵以x为根的子树,其左子树与右子树都是高度平衡的,而且它们的高度差之多是2,然后将以x为根的子树转变为高度平衡的。
将x进行左旋右旋来平衡左右子树。
c) 利用b)来描述一个递归程序AVL-INSERT(x,z),输入AVL树中的一个结点x以及一个新创建的结点z,然后把z添加到以x为根的子树中,并保持x是AVL树的根的性质。
向下递归插入结点z,回溯时若有不平衡的情况则调用BALANCE(x)。
d) 证明对一棵有n个结点的AVL树,AVL-INSERT操作要用O(lgn)时间,执行O(1)次旋转。
略
13-4 Treap
如果v是u的左孩子,则key[v]<key[u]。
如果v是u的右孩子,则key[v]>key[u]。
如果v是u的孩子,则priority[v]>priority[u]。
a) 证明:给定一个结点集合x1,x2,…,xn,它们关联了关键字和优先级,存在唯一的一棵treap与这些结点相关联。
由于树堆的性质3,根结点必定是最优先的结点,而比跟小的结点在左子树中,比根大的在右子树中。
同理左右子结点也必定是子树中最优先的结点,因此存在唯一的treap。
b) 证明:treap的期望高度是Θ(lgn),因此在treap内查找一个值所花的时间为Θ(lgn)。
c) 解释TREAP-INSERT如何工作。解释其思想并给出伪代码。
若插入的结点的比父结点更优先,则对父结点左旋/右旋。
d) 证明:TREAP-INSERT的期望运行时间是Θ(lgn)。
e) 考虑利用TREAP-INSERT来插入x后的treap T。令C为x左子树的右脊柱的长度。令D为x右子树的左脊柱的长度。证明:在插入x的期间所执行的旋转的总次数等于C+D。
f) 证明X(i,k)=1当且仅当priority[y]>priority[x],key[y]<key[x],而且对于每个满足key[y]<key[z]<key[x]的z,有priority[y]<priority[z]。
g) 证明:Pr{X_{i,k}=1}=(k-i-1)!/(k-i+1)!=1/(k-i+1)(k-1)
h) 证明: E[C]=sum_{j=1}^{k-1}1/j(j+1)=1-1/k
i) 利用对称性来证明:E[D]=1-1/(n-k+1)
j) 总结在将一个结点插入一棵treap内时,所执行的旋转的期望次数小于2。
class Treap{ public: struct Node{ Node* ch[2];//左右子树 int fix;//优先级。数值越小,优先级越高 int key; int size;//以它为根的子树的总结点数 int cmp(int x) const{ if (x==key) return -1; return x<key?0:1; } //名次树 void maintain(){ size=1; if (ch[0]!=NULL) size+=ch[0]->size; if (ch[1]!=NULL) size+=ch[1]->size; } }; Node* root; Treap(){ srand(time(0)); root=NULL; } void removetree(Node* &t){ if (t->ch[0]!=NULL) removetree(t->ch[0]); if (t->ch[1]!=NULL) removetree(t->ch[1]); delete t; t=NULL; } void clear(){ srand(time(0)); removetree(root); } Node* newNode(int v){ Node* t=new Node; t->key=v; t->ch[0]=t->ch[1]=NULL; t->fix=rand(); t->size=1; return t; } //d=0代表左旋,d=1代表右旋 void rotate(Node* &o,int d){ Node* k=o->ch[d^1]; o->ch[d^1]=k->ch[d]; k->ch[d]=o; o->maintain(); k->maintain(); o=k; } //在以o为根的子树中插入键值x,修改o void insert(Node* &o,int x){ if (o==NULL) o=newNode(x); else{ int d=o->cmp(x); if (d==-1) d=1; insert(o->ch[d],x); if (o->ch[d]->fix<o->fix) rotate(o,d^1); } o->maintain(); } void remove(Node* &o,int x){ int d=o->cmp(x); if (d==-1){ Node* u=o; if (o->ch[0]!=NULL&&o->ch[1]!=NULL){ int d2=(o->ch[0]->fix<o->ch[1]->fix?1:0); rotate(o,d2); remove(o->ch[d2],x); }else{ if (o->ch[0]==NULL) o=o->ch[1]; else if (o->ch[1]==NULL) o=o->ch[0]; delete u; } } else remove(o->ch[d],x); if (o!=NULL) o->maintain(); } bool find(Node* o,int x){ while (o!=NULL){ int d=o->cmp(x); if (d==-1) return 1; else o=o->ch[d]; } return 0; } //第k大的值 int kth(Node* o,int k){ if (o==NULL||k<=0||k>o->size) return 0; int s=(o->ch[1]==NULL?0:o->ch[1]->size); if (k==s+1) return o->key; else if (k<=s) return kth(o->ch[1],k); else return kth(o->ch[0],k-s-1); } void merge(Node* &src){ if (src->ch[0]!=NULL) merge(src->ch[0]); if (src->ch[1]!=NULL) merge(src->ch[1]); insert(root,src->key); delete src; src=NULL; } };