摘要:【笔记】一棵顺序统计量树T通过简单地在红黑树的每个结点存入附加信息而成。在一个结点x内,除了包含通常的红黑树的域key color p left right,还包括域size[x]。这个域中包含以结点x为根的子树的结点数(包括x本身),即子树的大小。如果定义哨兵为0,也就是设置size[nil]为0,则有等式 size[x]=size[left[x]]+size[right[x]]+1。在出现相等关键字的情况下,定义排序为按中序遍历树时输出的结点位置,以此消除顺序统计树原定义的不确定性。检索具有给定排序的元素:过程OS-SELECT(x,i)返回一个指向以x为根的子树中包含第i小关键字的结点的
阅读全文
摘要:【总结】template 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-...
阅读全文
摘要:【笔记】// 基本没搞懂 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 ...
阅读全文
摘要:【笔记】 void rbInsert(NODE *z) { NODE *y = nil; NODE *x = root; while (x != nil) { y = x; if (z->key key) { x = x->l; } else { x = x->r; } } z->p = y; if (y == nil) { root = z; } else { if (z->key key) { y->...
阅读全文
摘要:【笔记】旋转:保持二叉查找树性质的查找树局部操作。左旋: 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; } ...
阅读全文
摘要:【笔记】红黑树中每个结点包含五个域:color,key,left,right和p。如果某结点没有一个子结点或父结点,则该域指向NIL。我们把NIL视为二叉树的外结点(叶子),而带关键字的结点视为内结点。一棵二叉树如果满足下面的红黑性质,则为一棵红黑树:1) 每个结点或是红的,或是黑的。2) 根结点是黑的。3) 每个叶结点(NIL)是黑的。4) 如果一个结点是红的,则它的两个儿子都是黑的。5) 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。采用哨兵来代表NIL,它的color域为BLACK,其它域为任意值。从某个结点x出发(不包括该结点)到达一个叶结点的任意一条路径上,黑色结
阅读全文
摘要:【思考】12-1 具有相同关键字的二叉查找树a) 当用TREE-INSERT将n个具有相同关键字的数据项插入到一棵初始为空的二叉查找树中时,该算法的渐进性能如何?差不多O(n^2)b) 在结点x处设一个布尔标志b[x],并根据b[x]的不同值,置x为left[x]或right[x]。每当插入一个与x具有相同关键字的结点时,b[x]取反!O(nlogn)c) 在结点x处设置一个列表,其中所有结点都具有与x相同的关键字,并将z插入到该列表中。O(n^2)d) 随机地将x置为left[x]或right[x]。最坏O(n^2),平均O(nlogn)。12-2 基数树基数树,或称Patricia tri
阅读全文
摘要:【笔记】插入:从根结点开始,沿树下降。指针x跟踪这条路径,而y始终指向x的父结点。根据key[z]与key[x]的比较结果,决定向左向右转。直到x成为NIL为止。这个NIL所占位置及我们想插入z的地方,y即为z的父结点。删除:以指向z的指针为参数,考虑三种情况。若z没有子女,则修改其父结点p[z],是NIL为其子女;如果结点z只有一个子女,则可以通过在其子结点与父结点之间建立一条链来删除z。如果结点z有两个子女,先删除z的后继y(它没有左子女),再用y的内容来替代z的内容。 void treeInsert(NODE *z) { NODE *y = NULL; ...
阅读全文
摘要:【笔记】对一棵高度为h的二叉查找树,动态集合操作SEARCH、MINIMUM、MAXIMUM、SUCCESSOR和PREDECESSOR等的运行时间均为O(h)。 NODE* treeSearch(NODE *rt,T k) { if (rt==NULL || k==rt->key) return rt; if (kkey) return treeSearch(rt->l,k); else return treeSearch(rt->r,k); } NODE* iterativeTreeSearch(NODE *rt,T k) { ...
阅读全文
摘要:【总结】终于刷到这里了。二叉查找树性质:设x为二叉查找树中的一个节点。如果y是x的左子树中的一个结点,则key[x]=key[y]。如果x是一棵包含n个结点的子树的根,则调用INORDER-TREE-WALK(x)过程的时间为Θ(n)。 void inoderTreeWalk(NODE *rt) { if (rt->l) inoderTreeWalk(rt->l); coutkeyr) inoderTreeWalk(rt->r); }【练习】12.1-1 基于关键字集合{1,4,5,10,16,17,21},画出高度为2、3、4、5、6的二叉查找树。12...
阅读全文
摘要:【总结】证明题实在是太多,本章练习搞起来很吃力。但是代码却相当好写。重点在散列函数的设计。【思考】11-1 最长探查的界11-2 链接法中槽的大小11-3 二次探查11-4 k全域散列和认证
阅读全文
摘要:【笔记】开放寻址法:所有元素都存放在散列表里。当查找一个元素时,要检查所有的表项,直到找到所需的元素,或者最终发现该元素不在表中。删除操作:在槽中置一个特定的值DELETED。因此在必须删除关键字的应用中,往往采用链接法解决碰撞。计算开放寻址法中的探查序列:线性探查:h(k,i) = (h'(k)+i) mod m, i = 0,1,...,m-1二次探查:h(k,i) = (h'(k)+c1*i+c2*i^2) mod m双重散列:h(k,i) = (h1(k)+i*h2(k)) mod m双重散列是用于开放寻址法最好的方法之一,因为它所产生的排列具有随机选择的排列的许多特性
阅读全文
摘要:【笔记】好的散列函数:每个关键字都等可能地散列到m个槽位中的任何一个之中去,并与其他的关键字已被散列到哪一个槽位中无关。如果所给关键字不是自然数,则必须有一种方法来将它们解释为自然数。除法散列表:h(k) = k mod m。乘法散列表:h(k) = floor(m(kA mod 1)),用关键字k乘上常数A(0<A<1),并抽取出kA的小数部分。然后,用m乘以这个值,再取结果的底。【练习】11.3-1 假设我们希望查找一个长度为n的链表,其中每一个元素都包含一个关键字k和一个散列值h(k)。每一个关键字都是长字符串。在表中查找具有给定关键字的元素时,如何利用各元素中的散列值。先求
阅读全文
摘要:【笔记】函数h将关键字域U映射到散列表T[0..m-1]的槽位上。h(k)是关键字k的散列值。碰撞:两个关键字映射到同一个槽上。链接法:把散列到同一槽中的所有元素都放在一个链表中。给定一个能存放n个元素的、具有m个槽位的散列表T,定义T的装载因子α为n/m,即一个链中平均存储的元素数。对一个用链接技术来解决碰撞的散列表,在简单一致散列的假设下,一次不成功查找的期望时间为θ(1+α)。在简单一致散列的假设下,对于用链接技术解决碰撞的散列表,平均情况下一次成功的查找需要θ(1+α)时间。平均来说,查找操作需要常数常量的时间。【练习】11.2-1 假设用一个散列函数h,将n个不同的关键字散列到一个长
阅读全文
摘要:【笔记】当关键字的全域U比较小时,直接寻址。【练习】11.1-1 考虑由一个长度为m的直接寻址表T表示的动态集合S。给出一个查找S的最大元素的算法过程。所给的过程在最坏情况下的运行时间是什么?遍历动态集合中的所有元素O(m)11.1-2 位向量是一种仅包含0和1的数组。长度为m的位向量所占空间要比包含m个指针的数组少得多。请说明如何用一个位向量来表示一个包含不同元素(无卫星数据)的动态集合。字典操作的运行时间应该是O(1)。B[i]为1表示元素i存在,为0则不存在。11.1-3 说明如何实现一个直接寻址表,使各元素的关键字都不必相同,且各元素可以有卫星数据。所有三种字典操作(INSERT,DE
阅读全文
摘要:【思考】10-1 列表之间的比较每一种动态集合操作的渐进最坏情况运行时间是什么?10-2 用链表实现的可合并堆a) 链表是排序的b) 链表是不排序的c) 链表是不排序的,且待合并的各动态集合是不想交的。10-3 在已排序的紧凑链表中搜索a)
阅读全文
摘要:【笔记】用二叉数表示有多个子女的树:left[x]表示最左边的儿子,right[x]右边的兄弟。【练习】10.4-1 画出由下列域表示的、根在下标6处的二叉树。10.4-2 请写出一个O(n)时间的递归过程,在给定含n个结点的二叉树后,它可以将树中的每个结点的关键字输出来。void dfs(int x){ printf("%d ",key[x]); if (l[x]) dfs(l[x]); if (r[x]) dfs(r[x]);}10.4-3 请写出一个O(n)时间的非递归过程,将给定的n结点二叉树中每个结点的关键字输出来。可以利用栈作为辅助数据结构。void outpu
阅读全文
摘要:【笔记】对象的多重数组表示:对一组具有相同域的对象,每一个域都可以用一个数组来表示。对象的单数组表示:一个对象占据存储中的一组连续位置,指针即指向某对所占存储区的第一个位置,后续位置可以通过加上相应的偏移量进行寻址。分配和释放对象:使用废料收集器。【练习】10.3-1 请画出序列 存储在以多重数组表示的双链表中的形式,令画出在单数组表示下的形式。10.3-2 对一组用单数组表示实现的同构对象,写出其过程ALLOCATE-OBJECT和FREE-OBJECT。10.3-3 在过程ALLOCATE-OBJECT和FREE-OBJECT的实现中,为什么不需要重置对象的prev域。分配内存时只需要找.
阅读全文
摘要:【笔记】哨兵不能降低各种操作的渐进时间界,但可以降低常数因子,简化代码。template class list {private: struct DATA{ ITEM key; DATA *prev, *next; DATA() {} DATA(ITEM x):key(x) {} }NIL; DATA *nil; int sz;public: list(){ nil = &NIL; nil->next=nil; nil->prev=nil; } ~list...
阅读全文
摘要:【笔记】栈:后进先出 LIFO队列:先进先出 FIFOtemplateclass stack {private: ITEM S[MAXN]; int tp;public: stack() { tp = 0; } bool empty() { return tp == 0; } void push(ITEM x) { if (tp == MAXN) throw "stack overflow"; S[tp++] = x; } ITEM top() { if (tp == 0...
阅读全文
摘要:【思考】9-1 已排序的i个最大数给定一个含n个元素的集合,我们希望能用一个基于比较的算法来找出按顺序排列的i个最大元素。请找出能实现下列每一种方法的、具有最佳的渐进最坏运行时间的算法,并分析各种方法的运行时间(用n和i表示)。a) 对输入数排序,并列出i个最大的数。O(nlogn)的排序。b) 对输入数建立一个优先级队列,并调用EXTRACT-MAX过程i次。建堆O(n),每次调用O(logn),共O(n+ilogn)c) 利用一个顺序统计量算法来找到第i个最大元素,然后划分输入数组,再对i个最大数排序。寻找第i大元素O(n),排序O(ilogi),共O(n+ilogi)9-2 带权中位数对
阅读全文
摘要:【笔记】SELECT最坏运行情况是线性的。【练习】9.3-1 在算法SELECT中,输入元素被分为每组5个元素。如果它们被分为每组7个元素,该算法会仍然以线性时间工作吗?证明如果分成每组3个元素,SELECT无法在线性时间内运行。考虑每组分为k个元素。则大于或小于中位数的中位数的数至少为因此在最坏情况下SELECT将处理最多n-(n/4-k)=3n/4+k个元素。递归式用代换法解决,假设算法是线性的,猜测有T(n)=4时成立。因此该算法对4个及4个以上的分组是线性的。当k=3时该算法的复杂度是Ω(nlgn)。9.3-2 分析SELECT,并证明如果n>=140,则至少有n/4个元素大于中
阅读全文
摘要:【笔记】在平均情况下,任何顺序统计(特别是中位数),都可以在线性时间内得到。int partition(int A[],int p,int r) { int x = A[r]; int i = p-1; for (int j=p;jp+1时,若q在边界处,即q=r或q=p,若答案在A[q]处,转到情景1。否则转到情景3或情景2。因此递归过程中不会出现长度为0的数组。9.2-2 证明:指示器随机变量Xk和T(max(k-1,n-k))是独立的。9.2-3 写出RANDOMIZED-SELECT的一个迭代版本。int NonRecursiveRandomizedSelect(in...
阅读全文
摘要:【笔记】对于确定最小值问题,可以得到n-1次比较的这一下界。同时找出最小值和最大值,至多3*(n/2)次比较足以。将输入成对比较,较小者与最小值比较,较大者与最大值比较,每个元素3次比较。【练习】9.1-1 证明:在最坏情况下,利用n+seil(lgn)-2次比较,即可找到n个元素中的第2小元素。对元素两两进行比较,找出最小值,共n-1次比较。此时形成一个二叉树,高度为logn,根为最小元素。则次小元素一定在最小元素上升过程中的比较值中。即logn-1个比较值,因此最坏情况用n+seil(lgn)-2次比较即可找到次小元素。*9.1-2 证明:在最坏情况下,同时找到n个数字中的最大值和最小值需
阅读全文
摘要:【思考】8-1 比较排序的平均情况下界8-2 以线性时间原地置换排序有一个由n个数据记录组成的数组要排序,且每个记录的关键字为0或1。1) 算法的运行时间为O(n)2) 算法是稳定的。3) 算法是原地进行排序的。a) 给出一个满足1、2的算法。计数排序。b) 给出一个满足1、3的算法。采用类似快速排序中的PARTITION的方法,用两个指针分别从前往后、从后往前扫描,若(i>=j)则推出循环,否则将前面的第一个1与后面的最后一个0交换。c) 给出一个满足2、3的算法。所有稳定的原地排序算法皆可。d) 在a、b、c中给出的算法能否用来在O(bn)时间内,对有b位关键字的n个记录进行基数排序
阅读全文
摘要:【笔记】桶排序假设元素均匀而独立的分布在区间[0,1)上。桶排序的思想就是把区间[0,1)划分成n个相同大小的子区间,或称桶。将n个输入数分布到各个桶中去。整个桶排序算法以线性期望时间排序。void bucketSort(double A[],int n) { list B[MAXN]; for (int i=1;i::iterator it=B[i].begin();it!=B[i].end();it++) A[++cnt] = *it; }}【练习】8.4-1 说明BUCKET-SORT作用于数组 A = 。i = 0i = 1 0.13 0.16i = 2 0.2i =...
阅读全文
摘要:【笔记】基数排序按最低有效位数字进行排序。给定n个d位数,每一个数位可取k种可能的值。如果所用的稳定排序需要Θ(d(n+k))的时间正确地堆这些数进行排序。给定n个b位数和任何正整数r<=b,如果采用稳定排序需要Θ(n+k)时间,那么RADIX-SORT能在Θ((b/r)(n+2^r))时间内正确的对这些数进行排序。【练习】8.3-1 示出基数排序RADIX-SORT作用于下列英语单词上的过程:"COW","DOG","SEA","RUG","ROW","MOB",&q
阅读全文
摘要:【笔记】计数排序假设n个输入元素中的每一个都是介于0到k之间的整数,此处k为某个整数。当k=O(n)时,计数排序的运行时间为Θ(n)。计数排序是稳定的。void countingSort(int A[],int B[],int n,int k) { int C[MAXK]; for (int i=0;i=1;j--) { B[C[A[j]]] = A[j]; C[A[j]]--; }}【练习】8.2-1 说明COUNTING-SORT在数组 A = 上的处理过程。C = C = C[i]表示最后一个值为i的元素应在的位置。倒序将A[i]中的元素复...
阅读全文
摘要:【笔记】任意一个比较排序算法在最坏情况下,都需要做Ω(nlogn)次的比较。堆排序和合并排序都是渐进最优的比较排序算法,运行上界O(nlogn)与最坏情况下界Ω(nlogn)是一致的。【练习】8.1-1 在于某种比较排序算法对应的决策树中,一个叶结点最小可能的深度是多少?8.1-2 不用斯特林近似公式,给出log(n!)渐进紧缺界。8.1-3 证明:对于长度为n的n!种输入中的至少一半而言,不存在具有线性运行时间的比较排序算法。对n!中的1/n部分而言又怎样呢?1/2^n部分呢?假设有m个结点存在线性运行时间的比较排序算法,由高度为h的二叉树至多有2^h个子结点,则2^h>=m。两边取对
阅读全文
摘要:【思考题】7-1 Hoare 划分的正确性a) 说明 HOARE-PARTITION 算法在数组 A = 上的运行过程,并说明在第 4~11 行中 while 循环的每一轮迭代后,数组中各元素的之和辅助值的情况。p=1, r=12[i=0] 13 19 9 5 12 8 7 4 11 2 6 21 [j=13]6 [i=1] 19 9 5 12 8 7 4 11 2 [j=11] 13 216 2 [i=2] 9 5 12 8 7 4 11 [j=10] 19 13 21i=10, j=9return j = 9b) 下标i和j满足这样的特点,即我们从不会访问数组A的、在子数组A[p..r].
阅读全文
摘要:【笔记】利用RANDOMIZED-PARTITION,快速排序算法期望的运行时间当元素值不同时,为O(nlgn)。【练习】7.4-1 证明:在递归式:中,T(n)=Ω(n^2)。采用代换法。猜测 T(n) <= c*n^2,c为某个常数。选择足够大的c,使得 c*(2*n-1)可以支配Θ(n), T(n) <= c*n^2成立。7.4-2 证明:快速排序的最佳情况运行时间为Ω(nlgn)。7.4-3 证明:在q=0,1,...,n-1区间上,当q=0或q=n-1时,q^2+(n-q-1)^2取得最大值。令x=q一元二次函数,开口向上,x=(n-1)/2 为对称轴。因为 0<=
阅读全文
摘要:【笔记】新的排序调用RANDOMIZED-PARTITION。int randomizedPartition(int A[],int p,int r) { int i = rand() % (r-p+1) + p; swap(A[r],A[i]); return partition(A,p,r);}【练习】7.3-1 我们为什么要分析一个随机化算法的平均情况性能,而不是其最坏情况性能呢?随机化算法下输入较为平均,最坏情况极难出现。7.3-2 在过程RANDOMIZED-QUICKSORT的运行过程中,最坏情况下对随机数产生器RANDOM调用了多少次?最佳情况下调用了多少次?以...
阅读全文
摘要:【笔记】最佳情况划分:两个子问题的大小都不大于n/2。平衡的划分:每当按照常数比例进行划分时,总的时间都是O(nlogn)【练习】7.2-1 利用代换法证明:递归式T(n)=T(n-1)+⊙(n)的解为T(n)=⊙(n^2)。假设可以选择合适的常数C_1 C_2。得出T(n) = ⊙(n^2)。7.2-2 当数组A的所有元素都具有相同值时,QUICKSORT 的运行时间是什么?O(n^2)7.2-3 证明:当数组 A 包含不同的元素、且按降序排序时,QUICKSORT 的运行时间是⊙(n^2)。当元素按降序排列时,每次都是最大不对称划分,因此运行时间是⊙(n^2)。7.2-4 对几乎已拍好序的
阅读全文
摘要:【笔记】快速排序的最坏运行时间为O(n^2),期望运行时间为O(nlogn)。int partition(int A[],int p,int r) { int x = A[r]; int i = p-1; for (int j=p;j 上的过程。7.1-2 当数组 A[p..r] 中的元素均相同时,PARTITION 返回的q值是什么?修改 PARTITION,使得当数组 A[p..r] 中所有的元素值相同时,q = (p+r)/2。是r。if (i+1==r) return (p+r)/2;7.1-3 简要地证明在大小为n的子数组上,PARTITION的运行时间为⊙(n)。...
阅读全文
摘要:【完整代码】templateclass CHeap{private: Type *A; int heapSize; int length; inline int left(int x) { return x>1; }public: CHeap(){} CHeap(Type B[],int n) { init(B,n); } void init(Type B[],int n) { A = B; length = n; heapSize = 0; } void heapSort() ...
阅读全文
摘要:【笔记】一个最大优先队列支持以下操作:insert x:把元素x插入集合。maximum:返回集合中具有最大关键字的元素。extract-max:去掉并返回集合中具有最大关键字的元素。increase-key x k:将元素x的关键字增加到k,这里k值不能小于x的原关键字的值。 Type heapMaximum() { return A[1]; } Type heapExtractMax() { if (heapSize 1 && A[parent(i)] 执行 HEAP_EXTRACT_MAX 操作的过程。记录返回值为15,将最后一个元素移动到...
阅读全文
摘要:【笔记】反复将大根堆的根与最后一个结点交换,堆的大小减一,对根结点执行MAX_HEAPIFY维护堆的性质。最终A 数组按升序排列。 void heapSort() { buildMaxHeap(); for (int i=length;i>=2;i--) { swap(A[1],A[i]); heapSize--; maxHeapify(1); } }【练习】6.4-1 说明 HEAPSORT 在数组 A = 上的操作过程。A = 6.4-2 讨论在使用如下循环不变式时,H...
阅读全文
摘要:【笔记】从最后一个非叶子结点n/2开始,自底向上调用MAX_HEAPIFY建堆。证明:BUILD_MAX_HEAP 运行时间的界为O(n)一个n元素堆的高度为floor(logn),在任意高度h上,至多有ceil(n/2^(h+1))个结点。【练习】6.3-1 示出 BUILD_MAX_HEAP 作用于数组 A= 的过程。6.3-2 在 BUILD_MAX_HEAP 的第2行代码中,为什么希望循环下标i从 length[A]/2 降到1,而不是从1升到length[A]/2?MAX_HEAPIFY 仅适用于子树均为大根堆的情况,从1升到length[A]/2时,子树不为大根堆,不满足调用MAX
阅读全文
摘要:【笔记】当 MAX_HEAPIFY(A, i) 被调用时,我们假定LEFT(i)、RIGHT(i)为根的两棵二叉树都是最大堆。若子结点中有最大元素,将 A[i] 与其交换,对该子树调用 MAX_HEAPIFY。若 A[ i ] 为最大元素,结束循环。MAX_HEAPIFY 作用于一个高度为 h 的结点所需的运行时间为 O(h)。 void maxHeapify(int i) { int l = left(i); int r = right(i); int largest = i; if (l A[largest]) largest...
阅读全文
摘要:【笔记】堆是一棵完全二叉树,树的根为 A[ 1 ] ,给定某个节点的下标 i , 其父节点为 i/2 、左儿子为 i*2 、右儿子为 i*2+1。二叉堆有两种,最大堆、最小堆(或称大根堆、小根堆)。大根堆中 A[ PARENT(i) ] >= A[ i ] , 最大元素在根部。结点在根部的高度定义为从本结点到叶子的最长简单下降路径上边的数目。堆的高度为树的根的高度。【练习】6.1-1 在高度为h的堆中,最多和最少的元素个数是多少?最多:最少:6.1-2 证明:含n个元素的堆的高度为 logn6.1-3 证明:在一个最大堆的某棵子树中,最大元素在该子树的根上。假设存在一个子树,其最大元素不
阅读全文