得出下面递归的解。解应该包含递归允许的最严格的上界和下界。假设 T ( 1 ) = Θ ( 1 ) T(1)=\Theta(1) T(1)=Θ(1)
用两种方式解决part (a)、(b)、©,画一个递归树和应用主定理。用替代法解决part (d)
(a) T ( n ) = 4 T ( n 2 ) + O ( n ) T(n)=4T(\frac{n}{2})+\mathcal{O}(n) T(n)=4T(2n)+O(n)
递归树
深度 i 有 4 i 4^i 4i个顶点,每个最多 c n 2 i c\frac{n}{2^i} c2in次运算,因此深度 i 处最多 4 i c n 2 i 4^ic\frac{n}{2^i} 4ic2in次运算。整个树加和,所有运算至多:
∑ i = 0 log n 2 i c n = c n ∑ i = 0 log n 2 i = c n ( 2 log n + 1 − 1 ) < 2 c n 2 = O ( n 2 ) \sum_{i=0}^{\log n}2^icn=cn\sum_{i=0}^{\log n}2^i=cn(2^{\log n+1}-1)<2cn^2=\mathcal{O}(n^2) ∑i=0logn2icn=cn∑i=0logn2i=cn(2logn+1−1)<2cn2=O(n2)
因为每个叶子处运算为 Θ ( 1 ) \Theta(1) Θ(1),有 n 2 n^2 n2个叶子,所有运算至少 Ω ( n 2 ) \Omega(n^2) Ω(n2),得到 Θ ( n 2 ) \Theta(n^2) Θ(n2)运行时间
主定理
a=4,b=2,符合主定理的情况1,因为 log b a = 2 , f ( n ) = O ( n 2 − ϵ ) , ϵ ≤ 1 \log_ba=2,f(n)=\mathcal{O}(n^{2-\epsilon}),\epsilon\le1 logba=2,f(n)=O(n2−ϵ),ϵ≤1,
所以 T ( n ) = Θ ( n 2 ) T(n)=\Theta(n^2) T(n)=Θ(n2)
(b) T ( n ) = 3 T ( n 2 ) + O ( n 4 ) T(n)=3T(\frac{n}{\sqrt2})+\mathcal{O}(n^4) T(n)=3T(2n)+O(n4)
递归树
深度 i 有 3 i 3^i 3i个顶点,每个最多 c ( n 2 i ) 4 = c n 4 4 i c(\frac{n}{\sqrt2^i})^4=c\frac{n^4}{4^i} c(2in)4=c4in4次运算,因此深度 i 处最多 c 3 i 4 i n 4 c\frac{3^i}{4^i}n^4 c4i3in4次运算。整个树加和,所有运算至多:
∑ i = 0 2 log n c 3 i 4 i n 4 < ∑ i = 0 ∞ ( 3 4 ) i c n 4 = 4 c n 4 \sum_{i=0}^{2\log n}c\frac{3^i}{4^i}n^4<\sum_{i=0}^{\infty}(\frac{3}{4})^icn^4=4cn^4 ∑i=02lognc4i3in4<∑i=0∞(43)icn4=4cn4
因此 T ( n ) = O ( n 4 ) T(n)=\mathcal{O}(n^4) T(n)=O(n4)。 3 2 log n = n 2 log 3 3^{2\log n}=n^{2\log3} 32logn=n2log3个叶子节点,每个计算 Θ ( 1 ) \Theta(1) Θ(1),因此 T ( n ) T(n) T(n)至少 Ω ( n 2 log 3 ) \Omega(n^{2\log3}) Ω(n2log3)
主定理
a=3,b= 2 \sqrt2 2,选择 f ( n ) = Θ ( n 4 ) f(n)=\Theta(n^4) f(n)=Θ(n4),符合定理3, T ( n ) = O ( n 4 ) T(n)=\mathcal{O}(n^4) T(n)=O(n4)
因为 log b a = log 2 3 = 2 log 3 \log_ba=\log_{\sqrt2}3=2\log 3 logba=log23=2log3, n 4 = Ω ( n 2 log 3 + ϵ ) , ϵ ≤ ( 3 − 2 log 3 ) , n^4=\Omega(n^{2\log3+\epsilon}),\epsilon\le(3-2\log3), n4=Ω(n2log3+ϵ),ϵ≤(3−2log3),
且 3 ( n 2 ) 4 = 3 4 n 4 < c n 4 , 3 4 < c < 1 且3(\frac{n}{\sqrt2})^4=\frac{3}{4}n^4
选择 f ( n ) = 0 f(n)=0 f(n)=0,符合定理1, T ( n ) = Ω ( n 2 log 3 ) , 因为 log b a = log 2 3 = 2 log 3 T(n)=\Omega(n^{2\log3}),因为\log_ba=\log_{\sqrt2}3=2\log3 T(n)=Ω(n2log3),因为logba=log23=2log3
f ( n ) = Ω ( n 2 log 3 − ϵ ) , ϵ ≤ 2 log 3 f(n)=\Omega(n^{2\log3-\epsilon}),\epsilon\le2\log3 f(n)=Ω(n2log3−ϵ),ϵ≤2log3
(c) T ( n ) = 2 T ( n / 2 ) + 5 n log n T(n)=2T(n/2)+5n\log n T(n)=2T(n/2)+5nlogn
递归树
深度 i 有 2 i 2^i 2i个顶点,每个最多 5 n 2 i log n 2 i 5\frac{n}{2^i}\log\frac{n}{2^i} 52inlog2in次运算,因此深度 i 处最多 5 n log n 2 i 5n\log\frac{n}{2^i} 5nlog2in次运算。整个树加和,所有运算至多:
∑ i = 0 log 2 n 5 n ( log 2 n − i ) = 5 n log 2 n ( log 2 n + 1 ) 2 = Θ ( n log 2 2 n ) \sum_{i=0}^{\log_2n}5n(\log_2n-i)=5n\frac{\log_2n(\log_2n+1)}{2}=\Theta(n\log^2_2n) ∑i=0log2n5n(log2n−i)=5n2log2n(log2n+1)=Θ(nlog22n)
主定理
a=2,b=2,符合定理2, log b a = 1 , f ( n ) = Θ ( n log 2 n ) , T ( n ) = Θ ( n log 2 n ) \log_ba=1,f(n)=\Theta(n\log_2n),T(n)=\Theta(n\log^2n) logba=1,f(n)=Θ(nlog2n),T(n)=Θ(nlog2n)
(d) T ( n ) = T ( n − 2 ) + Θ ( n ) T(n)=T(n-2)+\Theta(n) T(n)=T(n−2)+Θ(n)
猜测 T ( n ) = c n 2 T(n)=cn^2 T(n)=cn2
c n 2 = c ( n − 2 ) 2 + Θ ( n ) = > c n 2 = c n 2 − 4 c n + 4 c + Θ ( n ) = > 4 c n − 4 c = Θ ( n ) cn^2=c(n-2)^2+\Theta(n)=>cn^2=cn^2-4cn+4c+\Theta(n)=>4cn-4c=\Theta(n) cn2=c(n−2)2+Θ(n)=>cn2=cn2−4cn+4c+Θ(n)=>4cn−4c=Θ(n)
因此 T ( n ) = n 2 T(n)=n^2 T(n)=n2
下面每种场景,选择一个最适合的排序算法(选择排序、插入排序、归并排序),并证明你 的选择。不要忘了:证明比选择分更多!每个排序可能不止被使用一次。如果你发现多个排序适用于一个场景,表明它们的优、缺点,选出最合适的。陈述并证明你做的任何假设,最好的应该由渐近运行时间进行评估。
(a)假设给定一个数据结构D,n个项目上有外部顺序,支持两个标准的序列操作:D.get_at(i)最坏运行时间 Θ ( 1 ) \Theta(1) Θ(1),D.set_at(i, x)最坏运行时间 Θ ( n log n ) \Theta(n\log n) Θ(nlogn)。选择一个算法对D中项目进行排序(in-place)
解:这部分需要一个In-place算法,因此不能选择归并排序,因为归并排序不是in-place排序。插入排序执行 O ( n 2 ) \mathcal{O}(n^2) O(n2)次get_at和 O ( n 2 ) \mathcal{O}(n^2) O(n2)次set_at操作,因此这个数据结构花费 O ( n 3 log n ) \mathcal{O}(n^3\log n) O(n3logn)时间。可选择地,选择排序执行 O ( n 2 ) \mathcal{O}(n^2) O(n2)次get_at操作,但仅需 O ( n ) \mathcal{O}(n) O(n)次set_at操作,因此这个数据结构最多花费 O ( n 2 log n ) \mathcal{O}(n^2\log n) O(n2logn)时间,因此我们选 选择排序。
(b)假设你有一个静态数组A,包含n个指向可比较对象的指针,每对花费 Θ ( log n ) \Theta(\log n) Θ(logn)时间比较。选择一个算法对A中指针进行排序,以便于指向的对象非降序。
解:对于这个问题,读和写花费常量时间,但比较是昂贵的, O ( log n ) \mathcal{O}(\log n) O(logn)。选择和插入排序都执行最坏情形 O ( n 2 ) \mathcal{O}(n^2) O(n2)次比较,而归并排序仅执行 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)次比较,因此我们选择归并排序。
(c)假设有一个有序数组A,包含n个整数,每个都占一个机器字。现在假设某人执行 log log n \log\log n loglogn次相邻项目的交换,让A不再有序。选择一个算法重排A中的整数。
解:选择排序和归并排序的性能不依赖输入;它们将执行 Θ ( n 2 ) \Theta(n^2) Θ(n2)和 Θ ( n log n ) \Theta(n\log n) Θ(nlogn)时间,无论输入是什么。插入排序,可以早一步中断内循环,因此可以在某些输入时,以 O ( n ) \mathcal{O}(n) O(n)时间运行。为了证明插入排序对于给定输入运行时间为 O ( n ) \mathcal{O}(n) O(n)时间,注意到:相邻项目间执行一次交换可以改变数组中反序的数量。可选择地,每次插入排序交换内循环中的两个项目,它修复一个反序。因此,如果一个数组在排序中有k次相邻交换,插入排序将以 O ( n + k ) \mathcal{O}(n+k) O(n+k)时间执行。对于这个问题,因为 k = log log n = O ( n ) k=\log\log n=\mathcal{O}(n) k=loglogn=O(n),插入排序以 O ( n ) \mathcal{O}(n) O(n)时间运行,因此选 插入排序。
Jean-Locutus IIcard正在找他失去能力的朋友Datum,位于Gravity岛。该岛是一个窄条,从北到南n km。IIcard需要精确地找出离Datum最近的整数km位置,以便于他处于可见范围。幸运地,IIcard有一个跟踪设备,总是告诉他Datum在他当前位置的南边、还是在北边(伤心的是,不会告诉他有多远),以及一个心灵传送设备,允许他以常量时间跳到岛上的特定坐标。不幸地是,Gravity岛正在快速下沉。岛的地貌是:南北两端首先沉入水中,岛的中央后沉入水中。因此,如果他靠近岛的任意一端,IIcard快速找到Datum是更重要的,以免他短路。描述一个算法,如果Datum离岛两端最近k km(他离南/北,要么k km,要么(n-k)km),然后IIcard可以用他的心灵传送装置和跟踪装置,访问 O ( log k ) \mathcal{O}(\log k) O(logk)位置找到他。
解:我们可以泛化二分查找的方法,用来从两端快速查找。这个方法将指数地、交替地从岛的任意一端向心查找,直到IIcard经过Datum,然后使用正常的二分查找找出Datum的精确位置。特别地,告诉IIcard交替地传送到 2 i 2^i 2i和 n − 2 i n-2^i n−2i,i从0开始增长,直到找到Datum,或者直到经过Datum,例如: 2 j − 1 2^{j-1} 2j−1时,Datum被看到在北边, 2 j 2^j 2j时,Datum被看到在南边。 n − 2 j − 1 n-2^{j-1} n−2j−1时,Datum在南边, n − 2 j n-2^j n−2j时,Datum在北边。达成这种状态将花费 O ( j ) \mathcal{O}(j) O(j)时间,因为最多访问2j位置,那么在岛屿剩余的 2 j − 1 2^{j-1} 2j−1km二分查找,将至多花费 O ( j ) \mathcal{O}(j) O(j)时间。但因为要么 2 j − 1 < k < 2 j 2^{j-1}
MixBookTube.tv是一个服务:让观众们看某人玩视频游戏的时候聊天。每个观众标识一个已知的唯一整数ID。聊天包含一个线性流消息,每个由一个观众写出。观众们可以看到最近的k条聊天信息,k取决于他们屏幕的尺寸。有时观众会在聊天中违规,被弹窗禁言。当观众被禁言时,他们不仅不能在聊天中发消息,而且他们之前发的所有消息都会被从聊天中移除。
描述一个数据库,有效地实现MixBookTube.tv chat,支持下面的操作,n是操作发生时,数据库中所有观众(禁言或没禁言)的数量(所有操作应该是最坏情形时间)。
build(V):初始化一个有n=|V|个观众位于V的聊天室,用时 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)
send(v, m):发送来自观众v(没被禁言)的消息m到聊天室,用时 O ( log n ) \mathcal{O}(\log n) O(logn)
recent(k):返回k条最近没被删除的消息(或者小于k),用时 O ( k ) \mathcal{O}(k) O(k)
ban(k):禁言观众v,耗时 O ( n v + log n ) \mathcal{O}(n_v+\log n) O(nv+logn)删除所有消息, n v n_v nv是观众v被禁言前发送的消息数
解法:我们将通过保持两个数据结构实现数据库:
一个双向链表L,包含聊天室中所有未删除的消息序列(以时间顺序)
一个有序数组 S,包含一个键值对 ( v , p v ) (v,p_v) (v,pv),以v为键,v是观众的ID, p v p_v pv是一个指针(指向特定观众单链表 L v L_v Lv,存储指向L中所有node(包含观众v发送的消息)的指针)。我们将使用 p v p_v pv指向None来表明观众v已经被禁言。
为了支持build(V),初始化L为一个空链表,耗时 O ( 1 ) \mathcal{O}(1) O(1)时间,初始化S尺寸n=|V|,包含 ( v , p v ) (v,p_v) (v,pv),对应每个观众 v ∈ V v\in V v∈V,耗时 O ( n ) \mathcal{O}(n) O(n)。初始化空链表 L v L_v Lv,每个 v ∈ V v\in V v∈V耗时 O ( 1 ) \mathcal{O}(1) O(1),然后对S进行排序,耗时 O ( n log n ) \mathcal{O}(n\log n) O(nlogn),例如归并排序。这个操作总共花费最坏情形 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)时间,并且维持了数据库不变性。
为了支持 s e n d ( v , m ) send(v, m) send(v,m),通过查找S中的v耗时最坏情形 O ( log n ) \mathcal{O}(\log n) O(logn),如果 L v L_v Lv是非None(例如,用户未被禁言),插入m到一个node x在L的头部,耗时最坏情形常量时间。这个操作总共花费最坏情形 O ( log n ) \mathcal{O}(\log n) O(logn),并维持了数据库的不变性。
为了支持recent(k),简单地遍历L的前k个node,并返回它们的消息。只要数据库的不变性是正确的,这个操作直接返回请求的消息,耗时 O ( k ) \mathcal{O}(k) O(k)。
为了支持ban(v),通过在S中查找v,耗时最坏情形 O ( log n ) \mathcal{O}(\log n) O(logn),找到 L v L_v Lv。然后遍历 L v L_v Lv中的指针,找到L中的node x,通过重新链接L中的指针移除L中的x,耗时最坏情形常量时间。最后,设置 p v p_v pv指向None。这个操作是正确的,因为它维持了数据库不变性,执行耗时最坏情形 O ( n v + log n ) \mathcal{O}(n_v+\log n) O(nv+logn)。
Tim正在安排Tim Talks,一个讲座系列,允许Mit社区的任何人规划一个时间公开演讲。一个演讲请求时一个元组(s,t),s和t是演讲的起始、结束times,s Tim必须预留房间来用作演讲。房间预定是个三元组(k,s,t),对应:s和t之间预留k>0个房间。两个房间预定 ( k 1 , s 1 , t 1 ) (k_1,s_1,t_1) (k1,s1,t1)和 ( k 2 , s 2 , t 2 ) (k_2,s_2,t_2) (k2,s2,t2)是互不相交的,要么 t 1 ≤ s 2 t_1\le s_2 t1≤s2、要么 t 2 ≤ s 1 t_2\le s_1 t2≤s1。 是相连的,要么 t 1 = s 2 t_1=s_2 t1=s2、要么 t 2 = s 1 t_2=s_1 t2=s1。预定计划是一个有序的房间预定元组:规划中的每组房间预定是互不相交的,房间预定以起始时间升序出现在序列中,每对相邻的房间预定预留不同数量的房间。 给定一个演讲请求的集合R,对应唯一一个预定规划B(满足所有请求),比如规划预定足够房间来开演讲。比如,给定一组演讲请求 R = { ( 2 , 3 ) , ( 4 , 10 ) , ( 2 , 8 ) , ( 6 , 9 ) , ( 0 , 1 ) , ( 1 , 12 ) , ( 13 , 14 ) } R=\{(2,3),(4,10),(2,8),(6,9),(0,1),(1,12),(13,14)\} R={(2,3),(4,10),(2,8),(6,9),(0,1),(1,12),(13,14)},满足的房间预定: B = ( ( 1 , 0 , 2 ) , ( 3 , 2 , 3 ) , ( 2 , 3 , 4 ) , ( 3 , 4 , 6 ) , ( 4 , 6 , 8 ) , ( 3 , 8 , 9 ) , ( 2 , 9 , 10 ) , ( 1 , 10 , 12 ) , ( 1 , 13 , 14 ) ) . B=((1, 0, 2),(3, 2, 3),(2, 3, 4),(3, 4, 6),(4, 6, 8),(3, 8, 9),(2, 9, 10),(1, 10, 12),(1, 13, 14)). B=((1,0,2),(3,2,3),(2,3,4),(3,4,6),(4,6,8),(3,8,9),(2,9,10),(1,10,12),(1,13,14)). (a)给定两个预定规划 B 1 B_1 B1和 B 2 B_2 B2, n = ∣ B 1 ∣ + ∣ B 2 ∣ n=|B_1|+|B_2| n=∣B1∣+∣B2∣, B 1 B_1 B1和 B 2 B_2 B2对应两组请求 R 1 R_1 R1和 R 2 R_2 R2,描述一个 O ( n ) \mathcal{O}(n) O(n)时间算法来计算预定规划B,对应 R = R 1 ∪ R 2 R=R_1\cup R_2 R=R1∪R2。 解法:为了归并两个预定规划,我们的方法将维持一个time x(初始化0),以及一个预定规划B(初始化为空),因此B是到time x为止满足 R 1 ∪ R 2 R_1 \cup R_2 R1∪R2的预定。在初始化时,这个不变性毫无疑问是正确的,因为所有时间是正数,只要我们提升x时保证不变性,一旦x超过任意请求的最后结束时间,B将会是涵盖所有 R 1 ∪ R 2 R_1 \cup R_2 R1∪R2的预定计划(除了可能:相邻的预定保留着相同数量的房间,其它都满足)。在这个处理过程中,我们也维持索引 i 1 i_1 i1和 i 2 i_2 i2(初始化为0),分别位于规划 B 1 B_1 B1和 B 2 B_2 B2,每个对应规划中最早的房间预定,它的结束时间肯定在x之后。现在我们执行一个重复的循环,提升x、把新的预定请求追加到B,满足不变性,直到 B 1 和 B 2 B_1和B_2 B1和B2所有请求已经被处理,比如, i 1 = ∣ B 1 ∣ 、 i 2 = ∣ B 2 ∣ i_1=|B_1|、i_2=|B_2| i1=∣B1∣、i2=∣B2∣。有5中情形: 其中一个规划已经结束(要么 i 1 = ∣ B 1 ∣ 、 i 2 = ∣ B 2 ∣ i_1=|B_1|、i_2=|B_2| i1=∣B1∣、i2=∣B2∣),因此从未耗尽预定中,采取下个预定请求(k,s,t),并拼接到B (k,max(s,x),t) 两个规划都没有结束: x和任一规划中的下个预定都没有重叠,因此提升x为任一预定中的最小开始时间。 time x之后,任一规划中下个预定(k,s,t)与另外规划中的预定,没有重叠,因此将(k,x,t)拼接到B,提升x到t,并增加相关规划的索引 任一规划的下个预定与另外规划中的预定(k’,s’,t’)从s’>x开始重叠,因此拼接(k,x,s’)到B,并提升x到s’ 两个规划中的预定(k,s,t)、(k’,s’,t’),从time x开始重叠,直到 t ∗ = m i n ( t , t ′ ) t*=min(t,t') t∗=min(t,t′),因此拼接(k+k’,x,t*)到B,提升x到t*,并提升预留房间结束时间为time t*的规划索引i的值。 这个过程维持了上面断言的不变性,因此直到结束,B涵盖R中所有预定请求。上面每次循环中执行常量工作,因为x每次循环提升为一个较大的时间(来自集合 B 1 、 B 2 B_1、B_2 B1、B2中找到的 O ( n ) \mathcal{O}(n) O(n)个时间),这个过程花费 O ( n ) \mathcal{O}(n) O(n)时间。 最后,为了使预定满足,我们遍历预定,将相邻、且有相同房间数的预定相结合,耗费 O ( n ) \mathcal{O}(n) O(n)。 (b)给定一个集合R,有n个演讲请求,描述一个 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)时间算法,来返回满足R的预定规划。 解法:将R分割为两个 Θ ( ∣ R ∣ ) \Theta(|R|) Θ(∣R∣)尺寸的集合 R 1 R_1 R1和 R 2 R_2 R2,并递归计算预定规划 B 1 B_1 B1和 B 2 B_2 B2(对应满足 R 1 和 R 2 R_1和R_2 R1和R2)。然后使用(a)计算满足R的预定B,耗费 O ( n ) \mathcal{O}(n) O(n)。作为基准情形,当 ∣ R ∣ = 1 、 R = ( s , t ) |R|=1、R={(s,t)} ∣R∣=1、R=(s,t),满足的预定是((1,s,t),),因此返回它耗时 Θ ( 1 ) \Theta(1) Θ(1)。这个算法遵循递归 T ( n ) = 2 T ( n / 2 ) + O ( n ) T(n)=2T(n/2)+\mathcal{O}(n) T(n)=2T(n/2)+O(n),因此通过主定理情形2得知,这个算法执行耗时 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)。 (c)写一个python函数satisfying_booking®,实现算法。
def merge_bookings(B1, B2):
n1, n2, i1, i2 = len(B1), len(B2), 0, 0
x = 0
B = []
while i1 + i2 < n1 + n2:
if i1 < n1: k1, s1, t1 = B1[i1]
if i2 < n2: k2, s2, t2 = B2[i2]
if i2 == n2:
k, s, x = k1, max(x, s1), t1
i1 += 1
elif i1 == n1:
k, s, x = k2, max(x, s2), t2
i2 += 1
else:
if x < min(s1, s2):
x = min(s1, s2)
if t1 <= s2:
k, s, x = k1, x, t1
i1 += 1
elif t2 <= s1:
k, s, x = k2, x, t2
i2 += 1
elif x < s2:
k, s, x = k1, x, s2
elif x < s1:
k, s, x = k2, x, s1
else:
k, s, x = k1 + k2, x, min(t1, t2)
if t1 == x: i1 += 1
if t2 == x: i2 += 1
B.append((k, s, x))
B_ = [B[0]]
for k, s, t in B[1:]:
k_, s_, t_ = B_[-1]
if (k == k_) and (t_ == s)
B_.pop()
s = s_
B_.append((k, s, t))
return B_
def satisfying_booking(R):
if len(R) == 1:
s, t = R[0]
return ((1 , s, t),)
m = len(R) // 2
R1, R2 = R[:m], R[m:]
B1 = satisfying_booking(R1)
B2 = satisfying_booking(R2)
B = merge_bookings(B1, B2)
return tuple(B)