??xml version="1.0" encoding="utf-8" standalone="yes"?> Every bus in the Ekaterinburg city has a special man (or woman) called
conductor. When you ride the bus, you have to give money to the conductor.
We know that there are more then P% conductors and less then Q% conductors.
Your task is to determine a minimal possible number of Ekaterinburg citizens. q里我想针对L上的一个问题谈谈自q理解?/p>
问题很简单,求二q制?的个数。对于一个字节(8bitQ的变量Q求其二q制表示?1"的个敎ͼ要求法的执行效率尽可能的高?/p>
先来看看L上给出的几个法Q?/p>
解法一Q每ơ除二,看是否ؓ奇数Q是的话q计加一Q最后这个结果就是二q制表示?的个数?/p>
解法二,同样用到一个@环,只是里面的操作用位移操作化了?/p>
1: int Count(int v) 解法三,用到一个y妙的与操作,v & (v -1 )每次能消Mq制表示中最后一?Q利用这个技巧可以减一定的循环ơ数?/p>
解法四,查表法,因ؓ只有数据8bitQ直接徏一张表Q包含各个数?的个敎ͼ然后查表p。复杂度O(1)?/p>
1: int countTable[256] = { 0, 1, 1, 2, 1, ..., 7, 7, 8 }; 首先是对法的衡量上Q复杂度真的是唯一的标准吗Q尤其对于这U数据规模给定,而且很小的情况下Q复杂度其实是个比较ơ要的因素?/p>
查表法的复杂度ؓO(1)Q我用解法一Q@环八ơ固定,复杂度也是O(1)。至于数据规模变大,变成32位整型,那查表法自然也不合适了?/p>
其次Q我觉得既然是这样一个很的操作Q衡量的度也必然要,CPU旉周期可以作ؓ一个参考?/p>
解法一里有若干ơ整数加法,若干ơ整数除法(一般的~译器都能把它优化成位移Q,q有几个循环分支判断Q几个奇偶性判断(q个比较耗时_ҎCSAPP上的数据Q一般一个branch penalty得耗掉14个左右的cycleQ,加v来大概几十个cycle吧?/p>
再看解法四,查表法看gơ地址计算p解决Q但实际上这里用C个访存操作,而且W一ơ访存的时候很有可能那个数l不在cache里,q样一个cache missD的后果可能就是耗去几十甚至上百个cycleQ因讉K内存Q。所以对于这U“小操作”,q个法的性能其实是很差的?/p>
q里我再推荐几个解决q个问题的算法,?2位无W号整型Z?/p>
1: int Count(unsigned x) { q有一个更巧妙的HAKMEM法
1: int Count(unsigned x) { 因ؓ2^6 = 64Q也是?x_0 + x_1 * 64 + x_2 * 64 * 64 = x_0 + x_1 + x_2 (mod 63)Q这里的{号表示同余?/p>
q个E序只需要十条左x令,而且不访存,速度很快?/p>
由此可见Q衡量一个算法实际效果不单要看复杂度Q还要结合其他情况具体分析?/p>
关于后面的两道扩展问题,问题一是问32位整型如何处理,q个上面已经讲了?/p>
问题二是l定两个整数A和BQ问A和B有多位是不同的?/p>
q个问题其实是?问题多了一个步骤,只要先算出A和B的异或结果,然后求这个g1的个数就行了?br /> (by ZelluX http://m.tkk7.com/zellux) Ex 2.35 其实只要递归调用d数就行了 Ex 2.36
我只能说太挫了。。。精度问题搞了半天,看来点q是要尽量化成整型再啊?br />
q有个问题就是q*i是开区间q是闭区_MWrong Answer了无数次后ȝq了。。?br />
]]>
抽时间多做做Q提高下我可怜的法功底 >,<
]]>
2: {
3: int num = 0;
4: while (v) {
5: num += v & 0x01;
6: v >>= 1;
7: }
8: return num;
9: }
2:
3: int Count(int v) {
4: return countTable[v];
5: }
好了Q这是L上给出的四种ҎQ下面谈谈我的看法?/p>
2: x = x - ((x >> 1) & 0x55555555);
3: x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4: x = (x + (x >> 4)) & 0x0F0F0F0F;
5: x = x + (x >> 8);
6: x = x + (x >> 16);
7: return x & 0x0000003F;
8: }
q里用的是二分法Q两两一l相加,之后四个四个一l相加,接着八个八个Q最后就得到各位之和了?/p>
2: unsigned n;
3:
4: n = (x >> 1) & 033333333333;
5: x = x - n;
6: n = (n >> 1) & 033333333333;
7: x = x - n;
8: x = (x + (x >> 3)) & 030707070707;
9: x = modu(x, 63);
10: return x;
11: }
首先是将二进制各位三个一l,求出每组?的个敎ͼ然后盔R两组归ƈQ得到六个一l的1的个敎ͼ最后很巧妙的用?3取余得到了结果?/p>
M看来q本书还是很不错的,比较喜欢里面针对一个问题提Z同算法ƈ不断改进的风根{这里提Z点个人的理解Q望大家指正 ;-)
]]>
]]>
Ex 2.18
把一个列表倒过来。不习惯在lisp里用iterative方式 >,<(define (reverse items)
(define (reverse-iter i k)
(if (null? i)
k
(reverse-iter (cdr i)
(cons (car i) k))))
(reverse-iter items ()))
接下来几题都是Map-Reduce思想的应用(或者照书上的说法,用enumerator - filter - map - accumulatorq四个步骤操作一个listQ?br />
用到的几个函敎ͼ(define (filter predicate sequence)
(cond ((null? sequence) null)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (enumerate-interval low high)
(if (> low high)
null
(cons low (enumerate-interval (+ low 1) high))))
(define (enumerate-tree tree)
(cond ((null? tree) null)
((pair? tree)
(append (enumerate-tree (car tree))
(enumerate-tree (cdr tree))))
(else (list tree))))
enumrate-tree 的功能是遍历一个树状结构,把其中的所有叶子保存在一个list中?br />
Ex 2.34
利用Horner's rule计算多项式结果(q公式这几天q经常碰刎ͼ(define (hornel-eval x coefficient-sequence)
(accumulate (lambda (this-coeff higher-terms)
(+ this-coeff (* x higher-terms)))
0
coefficient-sequence))
数出一|中的叶子数。这题我的做法比较土Q没惛_map-reduce操作上的递归Q而是把叶子节点的值都Ҏ1然后一个篏加?/p>(define (count-leave tree)
(accumulate +
0
(map (lambda (x) 1)
(enumerate-tree tree))))
(define (count-leaves t)
(accumulate + 0 (map (lambda (x) (if (pair? x) (count-leaves x) 1)) t)))
可以理解矩阵各列之和吧(define (accumulate-n op init seqs)
(if (null? (car seqs))
null
(cons (accumulate op init (map car seqs))
(accumulate-n op init (map cdr seqs)))))
> (accumulate-n + 0 (list (list 1 2 3) (list 4 5 6) (list 7 8 9) (list 10 11 12)))
(22 26 30)
]]>
发信? styc (styc), 信区: Algorithm
标?? Re: 请问一下大家正则表辑ּ的时间复杂度
发信? 水木C (Wed Mar 26 20:37:02 2008), 站内
NFA构造O(n)Q匹配O(nm)
DFA构造O(2^n)Q最化O(kn'logn')QN'=O(2^n)Q,匚wO(m)
n=regex长度Qm=串长Qk=字母表大,n'=原始的dfa大小
大概是这样子?br />
I normally do not read comp.lang.c, but Jim McKie told me that ``'' had come up in comp.lang.c again. I have lost the version that was sent to netnews in May 1984, but I have reproduced below the note in which I originally proposed the device. (If anybody has a copy of the netnews version, I would gratefully receive a copy at research!td or td@research.att.com.)
To clear up a few points: I was at Lucasfilm when I invented the device.
Here then, is the original document describing Duff's device:
From research!ucbvax!dagobah!td Sun Nov 13 07:35:46 1983 Consider the following routine, abstracted from code which copies an array of shorts into the Programmed IO data register of an Evans & Sutherland Picture System II:
(Obviously, this fails if the count is zero.) It amazes me that after 10 years of writing C there are still little corners that I haven't explored fully. (Actually, I have another revolting way to use switches to implement interrupt driven state machines but it's too horrid to go into.)
Many people (even bwk?) have said that the worst feature of C is that switches don't break automatically before each case label. This code forms some sort of argument in that debate, but I'm not sure whether it's for or against.
yrs trly
>>... Dollars to doughnuts this
>>was written on a RISC machine.
>Nope. Bell Labs Research uses VAXen and 68Ks, mostly.
Received: by ucbvax.ARPA (4.16/4.13) id AA18997; Sun, 13 Nov 83 07:35:46 pst
Received: by dagobah.LFL (4.6/4.6b) id AA01034; Thu, 10 Nov 83 17:57:56 PST
Date: Thu, 10 Nov 83 17:57:56 PST
From: ucbvax!dagobah!td (Tom Duff)
Message-Id: <8311110157.AA01034@dagobah.LFL>
To: ucbvax!decvax!hcr!rrg, ucbvax!ihnp4!hcr!rrg, ucbvax!research!dmr, ucbvax!research!rob
send(to, from, count)
register short *to, *from;
register count;
{
do
*to = *from++;
while (--count>0);
}
The VAX C compiler compiles the loop into 2 instructions (a movw and a sobleq,
I think.) As it turns out, this loop was the bottleneck in a real-time animation playback program which ran too slowly by about 50%. The standard way to get more speed out of something like this is to unwind the loop a few times, decreasing the number of sobleqs. When you do that, you wind up with a leftover partial loop. I usually handle this in C with a switch that indexes a list of copies of the original loop body. Of course, if I were writing assembly language code, I'd just jump into the middle of the unwound loop to deal with the leftovers. Thinking about this yesterday, the following implementation occurred to me:
send(to, from, count)
register short *to, *from;
register count;
{
register n=(count+7)/8;
switch(count%8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while(--n>0);
}
}
Tom
]]>
?a >用二叉链表作Z叉树的存储结?/a>Ӟ因ؓ每个l点中只有指向其左、右儿子l点的指针,所以从Ml点出发只能直接扑ֈ该结点的左、右儿子。在一般情况下靠它无法直接扑ֈ该结点在某种遍历序下的前驱和后l点。如果在每个l点中增加指向其前驱和后l结点的指针Q将降低存储I间的效率?/p>
我们可以证明Q在n个结点的二叉链表中含有n+1个空指针。因为含n个结点的二叉链表中含有个指针Q除了根l点Q每个结炚w有一个从父结Ҏ向该l点的指针,因此一׃用了n-1个指针,所以在n个结点的二叉链表中含有n+1个空指针?/p>
因此可以利用q些I指针,存放指向l点在某U遍历次序下的前驱和后l点的指针。这U附加的指针UCؓU烦Q加上了U烦的二叉链表称?font face="楷体_GB2312">U烦链表Q相应的二叉树称?font face="楷体_GB2312">U烦二叉?/font>。ؓ了区分一个结点的指针是指向其儿子的指针,q是指向其前驱或后l点的线索,可在每个l点中增加两个线索标志。这PU烦二叉树结点类型定义ؓQ?/p>
type TPosition=^thrNodeType; thrNodeType=record Label:LabelType; ltag,rtag:0..1; LeftChild,RightChild:TPosition; end; |
其中ltag为左U烦标志Qrtag为右U烦标志。它们的含义?
例如?3(a)是一中序线索二叉树Q它的线索链表如?3(b)所C?/p>
(a)
(b)
?3 U烦二叉树及其线索链?/p>
?3(b)中,在二叉树的线索链表上增加了一个头l点Q其LeftChild指针指向二叉树的根结点,其RightChild指针指向中序遍历时的最后一个结炏V另外,二叉树中依中序列表的W一个结点的LeftChild指针Q和最后一个结点的RightChild指针都指向头l点。这像Z叉树建立了一个双向线索链表,既可从第一个结点vQ顺着后q行遍历Q也可从最后一个结点v着前驱q行遍历?/p>
如何在线索二叉树中找l点的前驱和后l点Q以?3的中序线索二叉树Z。树中所有叶l点的右链是U烦Q因此叶l点的RightChild指向该结点的后l点Q如?3中结?b"的后lؓl点"*"。当一个内部结点右U烦标志?Ӟ其RightChild指针指向其右儿子Q因此无法由RightChild得到其后l结炏V然而,׃序遍历的定义可知Q该l点的后l应是遍历其叛_树时讉K的第一个结点,卛_子树中最左下的结炏V例如在扄?*"的后l时Q首先沿x针找到其叛_树的根结?-"Q然后沿其LeftChild指针往下直臛_左线索标志ؓ1的结点,即ؓ其后l结?在图中是l点"c")。类似地Q在中序U烦树中扄点的前驱l点的规律是Q若该结点的左线索标志ؓ1Q则LeftChild为线索,直接指向其前q点,否则遍历左子树时最后访问的那个l点Q即左子树中最右下的结点ؓ其前q炏V由此可知,若线索二叉树的高度ؓhQ则在最坏情况下Q可?em>O(h)旉内找C个结点的前驱或后l结炏V在对中序线索二叉树q行遍历Ӟ无须像非U烦树的遍历那样Q利用递归引入栈来保存待访问的子树信息?/p>
对一非U烦二叉树以某种ơ序遍历使其变ؓ一늺索二叉树的过E称?font face="楷体_GB2312">二叉树的U烦?/font>。由于线索化的实质是二叉链表中的空指针改ؓ指向l点前驱或后l的U烦Q而一个结点的前驱或后l结点的信息只有在遍历时才能得到Q因此线索化的过E即为在遍历q程中修改空指针的过E。ؓ了记下遍历过E中讉Kl点的先后次序,可附设一个指针pre始终指向刚刚讉Kq的l点。当指针p指向当前讉K的结ҎQpre指向它的前驱。由此也可推知pre所指结点的后为p所指的当前l点。这样就可在遍历q程中将二叉树线索化。对于找前驱和后l结点这二种q算而言Q线索树优于非线索树。但U烦树也有其~点。在q行插h和删除操作时Q线索树比非U烦树的旉开销大。原因在于在U烦树中q行插h和删除时Q除了修改相应的指针外,q要修改相应的线索?/p>
另外我有q次比赛的测试数据和标程Q需要的朋友留言卛_?/p>
U黑树和x应用(real time application)中有价|而且使它们有在提供最坏情冉|保的其他数据l构中作为徏造板块的价|例如Q在计算几何中用的很多数据l构都可以基于红黑树?/p>
U黑树在持久数据l构之一Q它们用来构?a class="new" title="兌数组">兌数组?a class="new" title="集合(计算机科?">集合Q在H变之后它们能保持ؓ以前的版本。除了O(log n)的时间之外,U黑树的持久版本Ҏơ插入或删除需要O(log n)的空间?/p>
U黑树是 二叉查找?/a>Q颜色或U色?em>黑色。在二叉查找树强制一般要求以外,对于M有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色?/p>
性质2. Ҏ黑色?/p>
性质3. 所有叶子都是黑Ԍ包括NILQ?/p>
性质4. 每个U色节点的两个子节点都是黑色?从每个叶子到根的所有\径上不能有两个连l的U色节点)
性质5. 从Q一节点到其每个叶子的所有\径都包含相同数目的黑色节炏V?/p>
q些U束强制了红黑树的关键性质:
从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是q个树大致上是^衡的。因为操作比如插入、删除和查找某个值的最坏情冉|间都要求与树?
高度成比例,q个在高度上的理Z限允许红黑树在最坏情况下都是高效的,而不同于普通的
因ؓ每一个红黑树也是一个特化的二叉查找?/a>上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的属性需要少?O(log n))的颜色变?实际是非常快速的)和不过三次以二叉查找树的方?/a>?
加节点ƈ标记它ؓU色。(如果设ؓ黑色Q就会导致根到叶子的路径上有一条\上,多一个额外的黑节点,q个是很难调整的。但是设为红色节点后Q可能会D?
C个连l红色节点的冲突Q那么可以通过颜色调换Qcolor flipsQ和树旋转来调整。)
下面要进行什么操作取决于其他临近节点的颜艌Ӏ同人类的家族树中一P我们用术?em>叔父节点来指一个节点的父节点的兄弟节点。注? 在下面的C意图中Q将要插入的节点标ؓNQ?strong>N
操作
对于每一U情况,我们?CCZ代码来展C。通过下列函数Q可以找C个节点的叔父和祖父节?
node grandparent(node n) {
return n->parent->parent;
}
node uncle(node n) {
if (n->parent == grandparent(n)->left)
return grandparent(n)->right;
else
return grandparent(n)->left;
}
情Ş1: 新节?strong>N位于树的根上Q没有父节点。在q种情Ş下,我们把它重绘为黑色以满性质2[5]。因为它在每个\径上寚w节点数目增加一Q性质5[4]W合?/p>
void insert_case1(node n) {
if (n->parent == NULL)
n->color = BLACK;
else
insert_case2(n);
}
情Ş2: 新节点的父节?strong>P是黑Ԍ所以性质4[3]没有失效Q新节点是红色的Q。在q种情Ş下,树仍是有效的。性质5[4]受到威胁Q因为新节点N有两个黑色叶子儿子;但是׃新节?strong>N是红Ԍ通过它的每个子节点的路径都有同通过它所取代的黑色的叶子的\径同h目的黑色节点Q所以这个性质依然满?/p>
void insert_case2(node n) {
if (n->parent->color == BLACK)
return; /* 树仍旧有?*/
else
insert_case3(n);
}
注意: 在下列情形下我们假定新节Ҏ父节点Q因为父节点是红Ԍq且如果它是根,它就应当是黑艌Ӏ所以新节点L一个叔父节点,管在情??下它可能是叶子?/p>
情Ş3: 如果父节?strong>P和叔父节?strong>U二者都是红Ԍ则我们可以将它们两个重绘为黑色ƈ重绘父节点G为红?用来保持性质5[4])。现在我们的新节?strong>N有了一个黑色的父节?strong>P。因为通过父节?strong>P或叔父节?strong>U的Q何\径都必定通过父节点GQ在q些路径上的黑节Ҏ目没有改变。但是,U色的祖父节?strong>G的父节点也有可能是红色的Q这p反了性质4[3]。ؓ了解册个问题,我们在祖父节?strong>G上递归地进?strong>情Ş1的整个过E?/p> |
void insert_case3(node n) {
if (uncle(n) != NULL && uncle(n)->color == RED) {
n->parent->color = BLACK;
uncle(n)->color = BLACK;
grandparent(n)->color = RED;
insert_case1(grandparent(n));
}
else
insert_case4(n);
}
注意: 在余下的情Ş下,我们假定父节?strong>P 是其父亲G 的左子节炏V如果它是右子节点,情Ş4?strong>情Ş5中的?/em>?em>?/em>应当对调?/p>
情Ş4: 父节?strong>P是红色而叔父节?strong>U是黑色或~少; q有Q新节点N是其父节?strong>P的右子节点,而父节点P又是其父节点的左子节炏V在q种情Ş下,我们q行一?a class="new" title="树旋?>左旋?/a>调换新节点和其父节点的角? 接着Q我们按情Ş5处理以前的父节点P。这D某些路径通过它们以前不通过的新节点N或父节点P中的一个,但是q两个节炚w是红色的Q所以性质5[4]没有失效?/p>
情Ş5: 父节?strong>P是红色而叔父节?strong>U 是黑色或~少Q新节点N 是其父节点的左子节点Q而父节点P又是其父节点G的左子节炏V在q种情Ş下,我们q行针对父节点P 的一?a class="new" title="树旋?>x?/a>; 在旋转生的树中Q以前的父节?strong>P现在是新节点N和以前的父节点G 的父节点。我们知道以前的父节点G是黑Ԍ否则父节?strong>P׃可能是红艌Ӏ我们切换以前的父节?strong>P和祖父节?strong>G的颜Ԍl果的树满性质4[3]。性质5[4]也仍然保持满I因ؓ通过q三个节点中M一个的所有\径以前都通过父节点G Q现在它们都通过以前的父节点P。在各自的情形下Q这都是三个节点中唯一的黑色节炏V?/p>
注意插入实际上是原地法Q因Zq所有调用都使用?a class="new" title="N递归">N递归?/p>
如果需要删除的节点有两个儿子,那么问题可以被{化成删除另一个只有一个儿子的节点的问?/strong>Qؓ了表q方便,q里所指的儿子Qؓ非叶子节点的儿子Q。对于二叉查找树Q在删除带有两个非叶子儿子的节点的时候,我们扑ֈ要么在它的左子树中的最大元素、要么在它的叛_树中的最元素,q把它的D{Ud要删除的节点?如在q里所展示的那?。我们接着删除我们从中复制出值的那个节点Q它必定有少于两个非叶子的儿子。因为只是复制了一个D不q反M属性,q就把问题简化ؓ如何删除最多有一个儿子的节点的问题。它不关心这个节Ҏ最初要删除的节点还是我们从中复制出值的那个节点?/p>
在本文余下的部分中,我们只需要讨论删除只有一个儿子的节点(如果它两个儿子都为空Q即均ؓ叶子Q我们Q意将其中一个看作它的儿
?。如果我们删除一个红色节点,它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它Qƈ不会破坏属??。通过被删除节点的所有\
径只是少了一个红色节点,q样可以l箋保证属?。另一U简单情冉|在被删除节点是黑色而它的儿子是U色的时候。如果只是去除这个黑色节点,用它的红色儿
子顶替上来的话,会破坏属?Q但是如果我们重l它的儿子ؓ黑色Q则曄通过它的所有\径将通过它的黑色儿子Q这样可以l保持属??/p>
需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时?/strong>Q这是一U复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,U呼q个儿子?strong>NQ称呼它的兄?它父亲的另一个儿??strong>S 我们可以使用下列代码q行上述的概要步骤,q里的函?
void insert_case4(node n) {
if (n == n->parent->right && n->parent == grandparent(n)->left) {
rotate_left(n->parent);
n = n->left;
} else if (n == n->parent->left && n->parent == grandparent(n)->right) {
rotate_right(n->parent);
n = n->right;
}
insert_case5(n);
}
void insert_case5(node n) {
n->parent->color = BLACK;
grandparent(n)->color = RED;
if (n == n->parent->left && n->parent == grandparent(n)->left) {
rotate_right(grandparent(n));
} else {
/* Here, n == n->parent->right && n->parent == grandparent(n)->right */
rotate_left(grandparent(n));
}
}
[~辑] 删除
node sibling(node n) {
if (n == n->parent->left)
return n->parent->right;
else
return n->parent->left;
}
replace_node
替换 child
?n
在树中的位置。出于方便,在本章节中的代码假定空叶子被用不是 NULL 的实际节点对象来表示(?em>插入
void delete_one_child(node n) {
/* Precondition: n has at most one non-null child */
node child = (is_leaf(n->right)) ? n->left : n->right;
replace_node(n, child);
if (n->color == BLACK) {
if (child->color == RED)
child->color = BLACK;
else
delete_case1(child);
}
free(n);
}
如果 N 和它初始的父亲是黑色Q则删除它的父亲D通过 N 的\径都比不通过它的路径了一个黑色节炏V因q反了属?4Q树需要被重新q。有几种情况需要考虑:
情况 1: N 是新的根。在q种情况下,我们做完了。我们从所有\径去除了一个黑色节点,而新Ҏ黑色的,所以属性都保持着?/p>
void delete_case1(node n) {
if (n->parent == NULL)
return;
else
delete_case2(n);
}
注意: 在情???下,我们假定 N 是它父亲的左儿子。如果它是右儿子Q则在这些情况下?em>?/em>?em>?/em>应当对调?/p>
情况 2: S 是红艌Ӏ在q种情况下我们在N的父亲上做左旋{Q把U色兄弟转换成N的祖父。我们接着对调 N 的父亲和父的颜艌Ӏ尽所有的路径仍然有相同数目的黑色节点Q现?N 有了一个黑色的兄弟和一个红色的父亲Q所以我们可以接下去?4??情况来处理?它的新兄弟是黑色因ؓ它是U色S的一个儿子? |
void delete_case2(node n) {
if (sibling(n)->color == RED) {
n->parent->color = RED;
sibling(n)->color = BLACK;
if (n == n->parent->left)
rotate_left(n->parent);
else
rotate_right(n->parent);
}
delete_case3(n);
}
情况 3: N 的父二ӀS ?S 的儿子都是黑色的。在q种情况下,我们单的重绘 S 为红艌Ӏ结果是通过S的所有\? 它们是以前?/em>? q?N 的那些\径,都少了一个黑色节炏V因为删?N 的初始的父亲佉K过 N 的所有\径少了一个黑色节点,q事情都^衡了h。但是,通过 P 的所有\径现在比不通过 P 的\径少了一个黑色节点,所以仍然违反属?。要修正q个问题Q我们要从情?1 开始,?P 上做重新q处理?/p> |
void delete_case3(node n) {
if (n->parent->color == BLACK &&
sibling(n)->color == BLACK &&
sibling(n)->left->color == BLACK &&
sibling(n)->right->color == BLACK)
{
sibling(n)->color = RED;
delete_case1(n->parent);
}
else
delete_case4(n);
}
情况 4: S ?S 的儿子都是黑Ԍ但是 N 的父亲是U色。在q种情况下,我们单的交换 N 的兄弟和父亲的颜艌Ӏ这不媄响不通过 N 的\径的黑色节点的数目,但是它在通过 N 的\径上寚w色节Ҏ目增加了一Q添补了在这些\径上删除的黑色节炏V?/p> |
void delete_case4(node n) {
if (n->parent->color == RED &&
sibling(n)->color == BLACK &&
sibling(n)->left->color == BLACK &&
sibling(n)->right->color == BLACK)
{
sibling(n)->color = RED;
n->parent->color = BLACK;
}
else
delete_case5(n);
}
情况 5: S 是黑ԌS 的左儿子是红ԌS 的右儿子是黑Ԍ?N 是它父亲的左儿子。在q种情况下我们在 S 上做x转,q样 S 的左儿子成ؓ S 的父亲和 N 的新兄弟。我们接着交换 S 和它的新父亲的颜艌Ӏ所有\径仍有同h目的黑色节点Q但是现? N 有了一个右儿子是红色的黑色兄弟Q所以我们进入了情况 6。N 和它的父亲都不受q个变换的媄响?/p> |
void delete_case5(node n) {
if (n == n->parent->left &&
sibling(n)->color == BLACK &&
sibling(n)->left->color == RED &&
sibling(n)->right->color == BLACK)
{
sibling(n)->color = RED;
sibling(n)->left->color = BLACK;
rotate_right(sibling(n));
}
else if (n == n->parent->right &&
sibling(n)->color == BLACK &&
sibling(n)->right->color == RED &&
sibling(n)->left->color == BLACK)
{
sibling(n)->color = RED;
sibling(n)->right->color = BLACK;
rotate_left(sibling(n));
}
delete_case6(n);
}
情况 6: S 是黑ԌS 的右儿子是红Ԍ?N 是它父亲的左儿子。在q种情况下我们在 N 的父亲上做左旋{Q这?S 成ؓ N 的父亲和 S 的右儿子的父二Ӏ我们接着交换 N 的父亲和 S 的颜Ԍq S 的右儿子为黑艌Ӏ子树在它的根上的仍是同L颜色Q所以属?3 没有被违反。但是,N 现在增加了一个黑色祖? 要ُ N 的父亲变成黑Ԍ要么它是黑色?S 被增加ؓ一个黑色祖父。所以,通过 N 的\径都增加了一个黑色节炏V?/p> 此时Q如果一个\径不通过 NQ则有两U可能?
在Q何情况下Q在q些路径上的黑色节点数目都没有改变。所以我们恢复了属?4。在C意图中的白色节点可以是U色或黑Ԍ但是在变换前后都必须指定相同的颜艌Ӏ?/p> |
void delete_case6(node n) {
sibling(n)->color = n->parent->color;
n->parent->color = BLACK;
if (n == n->parent->left) {
/* Here, sibling(n)->color == BLACK &&
sibling(n)->right->color == RED */
sibling(n)->right->color = BLACK;
rotate_left(n->parent);
}
else
{
/* Here, sibling(n)->color == BLACK &&
sibling(n)->left->color == RED */
sibling(n)->left->color = BLACK;
rotate_right(n->parent);
}
}
同样的,函数调用都用了N递归Q所以算法是地?/a>。此外,在旋转之后不再做递归调用Q所以进行了恒定数目(最?3 ?的旋转?/p>
包含n个内部节点的U黑树的高度?O(log(n))?/p>
定义: 引理: 以节?em>v为根的子树有臛_2bh(v) − 1个内部节炏V?/p>
引理的证?通过归纳高度): 基础: h(v) = 0 如果v的高度是零则它必定是 nilQ因?bh(v) = 0。所? 2bh(v) − 1 = 20 − 1 = 1 − 1 = 0 归纳假设: h(v) = k ?em>v?2bh(v) − 1 − 1 个内部节ҎCZ h(v') = k+1 ?v'?bh(v') − 1 个内部节炏V?/p>
因ؓ v' ?h(v') > 0 所以它是个内部节点。同L它有黑色高度要么?bh(v') 要么?bh(v')-1 (依据v'是红色还是黑?的两个儿子。通过归纳假设每个儿子都有臛_ 2bh(v') − 1 − 1 个内部接点,所?v' ? 2bh(v') − 1 − 1 + 2bh(v') − 1 − 1 + 1 = 2bh(v') − 1 个内部节炏V?/p>
使用q个引理我们现在可以展示出树的高度是Ҏ性的。因为在从根到叶子的M路径上至有一半的节点是黑?ҎU黑树属?)Q根的黑色高度至是h(root)/2。通过引理我们得到: 因此根的高度是O(log(n))?/p>
渐进边界的证?/h2>
有n个大各不相同的螺帽及对应的螺钉Q要求在O(nlogn)的复杂度内完成配寏V螺钉之间、螺帽之间都无法直接比较大小Q只能比较一颗螺帽与螺钉Q判断他们之间的大小差异?br>
感觉和快速排序的partition差不多?/p>
首先任选一颗螺钉与各螺帽进行比较,分成大小两组Q另外得C改螺钉相匚w的螺帽?/p>
然后用那个螺帽再和其他螺钉比较,也分成大两l,然后l箋二分递归?/p>