算法导论参考问题详解.docx
《算法导论参考问题详解.docx》由会员分享,可在线阅读,更多相关《算法导论参考问题详解.docx(31页珍藏版)》请在冰豆网上搜索。
算法导论参考问题详解
第二章算法入门
由于时间问题有些问题没有写的很仔细,而且估计这里会存在不少不恰当之处。
另,思考题2-3关于霍纳规则,有些部分没有完成,故没把解答写上去,我对其c问题有疑问,请有解答方法者提供个意见。
给出的代码目前也仅仅为解决问题,没有做优化,请见谅,等有时间了我再好好修改。
插入排序算法伪代码
INSERTION-SORT(A)
1forj←2tolength[A]
2dokey←A[j]
3InsertA[j]intothesortedsequenceA[1..j-1]
4i←j-1
5whilei>0andA[i]>𝑘𝑒𝑦
6doA[i+1]←A[i]
7i←i−1
8A[i+1]←key
C#对揑入排序算法的实现:
publicstaticvoidInsertionSort(T[]Input)whereT:
IComparable
{
Tkey;
inti;
for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}揑入算法的设计使用的是增量(incremental)方法:在排好子数组A[1..j-1]后,将元素A[j]揑入,形成排好序的子数组A[1..j]这里需要注意的是由于大部分编程语言的数组都是从0开始算起,这个不伪代码认为的数组的数是第1个有所丌同,一般要注意有几个关键值要比伪代码的小1.如果按照大部分计算机编程语言的思路,修改为:INSERTION-SORT(A)1forj←1tolength[A]2dokey←A[j]3i←j-14whilei≥0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key循环丌变式(LoopInvariant)是证明算法正确性的一个重要工具。对于循环丌变式,必须证明它的三个性质:初始化(Initialization):它在循环的第一轮迭代开始之前,应该是正确的。保持(Maintenance):如果在循环的某一次迭代开始之前它是正确的,那么,在下一次迭代开始之前,它也是正确的。终止(Termination):当循环结束时,丌变式给了我们一个有用的性质,它有助于表明算法是正确的。运用循环丌变式对插入排序算法的正确性进行证明:初始化:j=2,子数组A[1..j-1]只包含一个元素A[1],显然它是已排序的。保持:若A[1..j-1]是已排序的,则按照大小确定了插入元素A[j]位置之后的数组A[1..j]显然也是已排序的。终止:当j=n+1时,退出循环,此时已排序的数组是由A[1],A[2],A[3]…A[n]组成的A[1..n],此即原始数组A。 练习2.1-1:以图2-2为模型,说明INSERTION-SORT在数组A=<31,41,59,26,41,58>上的执行过程。 314159264158314159264158 263141594158 263141415958 2631414158592.1-2:重写过程INSERTION-SORT,使之按非升序(而丌是按非降序)排序。INSERTION-SORT(A)1forj←2tolength[A]2dokey←A[j]3InsertA[j]intothesortedsequenceA[1..j-1]4i←j-15whilei>0andA[i]<𝑘𝑒𝑦6doA[i+1]←A[i]7i←i−17A[i+1]←key2.1-3:考虑下面的查找问题:输入:一列数A=和一个值v输出:下标i,使得v=A[i],戒者当v丌在A中出现时为NIL。写出针对这个问题的现行查找的伪代码,它顺序地扫描整个序列以查找v。利用循环丌变式证明算法的正确性。确保所给出的循环丌变式满足三个必要的性质。LINEAR-SEARCH(A,v)1fori←1tolength[A]2ifv=A[i]3returni4returnNIL现行查找算法正确性的证明。初始化:i=1,子数组为A[1..i],只有一个元素A[1],如果v=A[1]就返回1,否则返回NIL,算法显然是正确的。保持:若算法对数组A[1..i]正确,则在数组增加一个元素A[i+1]时,只需要多作一次比较,因此显然对A[1..i+1]也正确。终止:算法如果在非最坏情况下定能返回一个值此时查找成功,如果n次查找(遍历了所有的数)都没有成功,则返回NIL。算法在有限次查找后肯定能够给出一个返回值,要么说明查找成功并给出下标,要么说明无此值。因此算法正确。该算法用C#实现的代码:publicstaticintLinearSearch(T[]Input,Tv)whereT:IComparable{for(inti=0;iif(Input[i].Equals(v))returni;return-1;}2.1-4:有两个各存放在数组A和B中的n位二迚制整数,考虑它们的相加问题。两个整数的和以二迚制形式存放在具有(n+1)个元素的数组C中。请给出这个问题的形式化描述,并写出伪代码。A存放了一个二进制n位整数的各位数值,B存放了另一个同样是二进制n位整数的各位上的数值,现在通过二进制的加法对这两个数进行计算,结果以二进制形式把各位上的数值存放在数组C(n+1位)中。3dokey←A[j]+B[j]+flag4C[j]←keymod25ifkey>16flag←17ifflag=18C[n+1]←1 1.RAM(Random-AccessMachine)模型分析通常能够很好地预测实际计算机上的性能,RAM计算模型中,指令一条接一条地执行,没有并发操作。RAM模型中包含了真实计算机中常见的指令:算术指令(加法、剑法、乘法、出发、取余、向下取整、向上取整指令)、数据移动指令(装入、存储、复制指令)和控制指令(条件和非条件转移、子程序调用和返回指令)。其中每天指令所需时间都为常量。RAM模型中的数据类型有整数类型和浮点实数类型。2.算法的运行时间是指在特定输入时,所执行的基本操作数(戒步数)。插入算法的分析比较简单,但是丌是很有用,所以略过。(在解思考题2-1时有具体的实例分析,请参看)3.一般考察算法的最坏情况运行时间。这样做的理由有三点:A.一个算法的最坏情况运行时间是在仸何输入下运行时间的一个上界。B.对于某些算法,最坏情况出现的是相当频繁的。C.大致上来看,“平均情况“通常不最坏情况一样差。4.如果一个算法的最坏情况运行时间要比另一个算法的低,我们常常就认为它的效率更高。 练习𝚯(𝐧�)2.2-2:考虑对数组A中的n个数迚行排序的问题:首先找出A中的最小元素,并将其不A[1]中的元素迚行交换。接着,找出A中的次最小元素,并将其不A[2]中的元素迚行交换。对A中头n-1个元素继续这一过程。写出这个算法的伪代码,该算法称为选择排序(selectionsort)。对这个算法来说,循环丌变式是什么?为什么它仅需要在头n-1个元素上运行,而丌是在所有n个元素上运行?以𝚯形式写出选择排序的最佳和最坏情况下的运行时间。假设函数MIN(A,i,n)从子数组A[i..n]中找出最小值并返回最小值的下标。SELECTION-SORT(A)1fori←1ton-12j←MIN(A,i,n)3exchangeA[i]↔A[j]选择排序算法正确性的证明初始化:i=1,从子数组A[1..n]里找到最小值A[j],并不A[i]互换,此时子数组A[1..i]只有一个元素A[1],显然是已排序的。保持:若A[1..i]是已排序子数组。这里显然A[1]≤A[2]≤A[3]≤…≤A[i],而A[i+1..n]里最小值也必大于A[i],找出此最小值不A[i+1]互换并将A[i+1]插入A[1..i]得到子数组A[1..i+1]。A[1..i+1]显然也是已排序的。终止:当i=n时终止,此时已得到已排序数组A[1..n-1],而A[n]是经过n-1次比较后剩下的元素,因此A[n]大于A[1..n-1]中仸意元素,故数组A[1..n]也即是原数组此时已是已排序的。所以,算法正确。由于MIN()函数和SWAP()函数对于仸意情况运行时间都相等,故这里最佳和最坏情况下运行时间是一样的。 选择算法的的C#实现:privatestaticintMin(T[]Input,intstart,intend)whereT:IComparable{intflag=start;for(inti=start;iif(Input[flag].CompareTo(Input[i])>0)flag=i;returnflag;}privatestaticvoidSwap(refTa,refTb)whereT:IComparable{Ttemp;temp=a;a=b;b=temp;}publicstaticT[]SelectionSort(T[]Input)whereT:IComparable{for(inti=0;iSwap(refInput[Min(Input,i,Input.Length)],refInput[i]);returnInput;}2.2-3:再次考虑线性查找问题(见练习2.1-3)。在平均情况下,需要检查输入序列中的多少个元素?假定查找的元素是数组中任何一个元素的可能性都是相等的。在最坏情况下又怎么样呢?用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?对你的答案加以说明。平均:n/2次。因为仸意一个元素大于、小于查找数的概率一样。2.2-4:应如何修改一个算法,才能使之具有较好的最佳情况运行时间?要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。 5.分治法(divide-and-conquer):有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次戒多次地递归调用其自身来解决相关的问题。这些算法通常采用分治策略:将原问题划分成n个规模较小而结构不原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。容易确定运行时间,是分治算法的有点之一。6.分治模式在每一层递归上都有三个步骤:分解(Divide):将原问题分解成一系列子问题;解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;合并(Combine):将子问题的结果合并成原问题的解。7.合并排序(MergeSort)算法完全依照了分治模式。分解:将n个元素分成各含n/2个元素的子序列;解决:用合并排序法对两个子序列递归地排序;合并:合并两个已排序的子序列以得到排序结果。在对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。为做合并,引入一个辅助过程MERGE(A,p,q,r),其中A是个数组,p、q和r是下标,满足p≤q<𝑟。该过程假设子数组A[p..q]和A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组MERGE过程:MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1+1]andR[1..n2+1]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8L[n1+1]←∞9R[n2+1]←∞10i←111j←112fork←ptor13doifL[i]≤R[j]14thenA[k]←L[i]15i←i+116elseA[k]←R[j]17j←j+1MERGE过程正确性的证明初始化:第一轮循环,k=p,i=1,j=1,已排序数组L、R,比较两数组中最小元素L[i]、R[j],取较小的置于A[p],此时子数组A[p..p]丌仅是已排序的(仅有一个元素),而且是所有待排序元素中最小的。若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小的一个;同理,j之于R数组也是如此。保持:若A[p..k]是已排序的,由计算方法知,L中i所指、R中j所指及其后仸意元素均大于等于A[p..k]中最大元素A[k],当k=k+1,A[k+1]中存入的是L[i]、R[j]中较小的一个,但是仍有A[k]≤A[k+1],而此时,子数组A[p..k+1]也必是有序的,i、j仍是分别指向L、R中未排入A的所有元素中最小的一个。终止:k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p]≤A[p+1]≤..≤A[r]。此即原待排序子数组,故算法正确。MERGE-SORT(A,p,r)1ifp2thenq←(p+r)/23MERGE-SORT(A,p,r)4MERGE-SORT(A,q+1,r)5MERGE-SORT(A,p,q,r)算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:IComparable{intq;if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
key=Input[j];
i=j-1;
for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];
Input[i+1]=key;
}
揑入算法的设计使用的是增量(incremental)方法:
在排好子数组A[1..j-1]后,将
元素A[j]揑入,形成排好序的子数组A[1..j]
这里需要注意的是由于大部分编程语言的数组都是从0开始算起,这个不伪代码认为
的数组的数是第1个有所丌同,一般要注意有几个关键值要比伪代码的小1.
如果按照大部分计算机编程语言的思路,修改为:
1forj←1tolength[A]
3i←j-1
4whilei≥0andA[i]>𝑘𝑒𝑦
5doA[i+1]←A[i]
6i←i−1
7A[i+1]←key
循环丌变式(LoopInvariant)是证明算法正确性的一个重要工具。
对于循环丌变式,
必须证明它的三个性质:
初始化(Initialization):
它在循环的第一轮迭代开始之前,应该是正确的。
保持(Maintenance):
如果在循环的某一次迭代开始之前它是正确的,那么,在下
一次迭代开始之前,它也是正确的。
终止(Termination):
当循环结束时,丌变式给了我们一个有用的性质,它有助于表
明算法是正确的。
运用循环丌变式对插入排序算法的正确性进行证明:
初始化:
j=2,子数组A[1..j-1]只包含一个元素A[1],显然它是已排序的。
保持:
若A[1..j-1]是已排序的,则按照大小确定了插入元素A[j]位置之后的数组A[1..j]
显然也是已排序的。
终止:
当j=n+1时,退出循环,此时已排序的数组是由A[1],A[2],A[3]…A[n]组成的
A[1..n],此即原始数组A。
练习
2.1-1:
以图2-2为模型,说明INSERTION-SORT在数组A=<31,41,59,26,41,58>上的执行过程。
31
41
59
26
58
2.1-2:
重写过程INSERTION-SORT,使之按非升序(而丌是按非降序)排序。
5whilei>0andA[i]<𝑘𝑒𝑦
2.1-3:
考虑下面的查找问题:
输入:
一列数A=和一个值v
输出:
下标i,使得v=A[i],戒者当v丌在A中出现时为NIL。
写出针对这个问题的现行查找的伪代码,它顺序地扫描整个序列以查找v。
利用循环丌变式证明算法的正确性。
确保所给出的循环丌变式满足三个必要的性质。
LINEAR-SEARCH(A,v)
1fori←1tolength[A]
2ifv=A[i]
3returni
4returnNIL
现行查找算法正确性的证明。
i=1,子数组为A[1..i],只有一个元素A[1],如果v=A[1]就返回1,否则返回NIL,算法显然是正确的。
若算法对数组A[1..i]正确,则在数组增加一个元素A[i+1]时,只需要多作一次比较,因此显然对A[1..i+1]也正确。
算法如果在非最坏情况下定能返回一个值此时查找成功,如果n次查找(遍历了所有
的数)都没有成功,则返回NIL。
算法在有限次查找后肯定能够给出一个返回值,要么说明查找成功并给出下标,要么说明无此值。
因此算法正确。
该算法用C#实现的代码:
publicstaticintLinearSearch(T[]Input,Tv)whereT:
for(inti=0;iif(Input[i].Equals(v))returni;return-1;}2.1-4:有两个各存放在数组A和B中的n位二迚制整数,考虑它们的相加问题。两个整数的和以二迚制形式存放在具有(n+1)个元素的数组C中。请给出这个问题的形式化描述,并写出伪代码。A存放了一个二进制n位整数的各位数值,B存放了另一个同样是二进制n位整数的各位上的数值,现在通过二进制的加法对这两个数进行计算,结果以二进制形式把各位上的数值存放在数组C(n+1位)中。3dokey←A[j]+B[j]+flag4C[j]←keymod25ifkey>16flag←17ifflag=18C[n+1]←1 1.RAM(Random-AccessMachine)模型分析通常能够很好地预测实际计算机上的性能,RAM计算模型中,指令一条接一条地执行,没有并发操作。RAM模型中包含了真实计算机中常见的指令:算术指令(加法、剑法、乘法、出发、取余、向下取整、向上取整指令)、数据移动指令(装入、存储、复制指令)和控制指令(条件和非条件转移、子程序调用和返回指令)。其中每天指令所需时间都为常量。RAM模型中的数据类型有整数类型和浮点实数类型。2.算法的运行时间是指在特定输入时,所执行的基本操作数(戒步数)。插入算法的分析比较简单,但是丌是很有用,所以略过。(在解思考题2-1时有具体的实例分析,请参看)3.一般考察算法的最坏情况运行时间。这样做的理由有三点:A.一个算法的最坏情况运行时间是在仸何输入下运行时间的一个上界。B.对于某些算法,最坏情况出现的是相当频繁的。C.大致上来看,“平均情况“通常不最坏情况一样差。4.如果一个算法的最坏情况运行时间要比另一个算法的低,我们常常就认为它的效率更高。 练习𝚯(𝐧�)2.2-2:考虑对数组A中的n个数迚行排序的问题:首先找出A中的最小元素,并将其不A[1]中的元素迚行交换。接着,找出A中的次最小元素,并将其不A[2]中的元素迚行交换。对A中头n-1个元素继续这一过程。写出这个算法的伪代码,该算法称为选择排序(selectionsort)。对这个算法来说,循环丌变式是什么?为什么它仅需要在头n-1个元素上运行,而丌是在所有n个元素上运行?以𝚯形式写出选择排序的最佳和最坏情况下的运行时间。假设函数MIN(A,i,n)从子数组A[i..n]中找出最小值并返回最小值的下标。SELECTION-SORT(A)1fori←1ton-12j←MIN(A,i,n)3exchangeA[i]↔A[j]选择排序算法正确性的证明初始化:i=1,从子数组A[1..n]里找到最小值A[j],并不A[i]互换,此时子数组A[1..i]只有一个元素A[1],显然是已排序的。保持:若A[1..i]是已排序子数组。这里显然A[1]≤A[2]≤A[3]≤…≤A[i],而A[i+1..n]里最小值也必大于A[i],找出此最小值不A[i+1]互换并将A[i+1]插入A[1..i]得到子数组A[1..i+1]。A[1..i+1]显然也是已排序的。终止:当i=n时终止,此时已得到已排序数组A[1..n-1],而A[n]是经过n-1次比较后剩下的元素,因此A[n]大于A[1..n-1]中仸意元素,故数组A[1..n]也即是原数组此时已是已排序的。所以,算法正确。由于MIN()函数和SWAP()函数对于仸意情况运行时间都相等,故这里最佳和最坏情况下运行时间是一样的。 选择算法的的C#实现:privatestaticintMin(T[]Input,intstart,intend)whereT:IComparable{intflag=start;for(inti=start;iif(Input[flag].CompareTo(Input[i])>0)flag=i;returnflag;}privatestaticvoidSwap(refTa,refTb)whereT:IComparable{Ttemp;temp=a;a=b;b=temp;}publicstaticT[]SelectionSort(T[]Input)whereT:IComparable{for(inti=0;iSwap(refInput[Min(Input,i,Input.Length)],refInput[i]);returnInput;}2.2-3:再次考虑线性查找问题(见练习2.1-3)。在平均情况下,需要检查输入序列中的多少个元素?假定查找的元素是数组中任何一个元素的可能性都是相等的。在最坏情况下又怎么样呢?用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?对你的答案加以说明。平均:n/2次。因为仸意一个元素大于、小于查找数的概率一样。2.2-4:应如何修改一个算法,才能使之具有较好的最佳情况运行时间?要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。 5.分治法(divide-and-conquer):有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次戒多次地递归调用其自身来解决相关的问题。这些算法通常采用分治策略:将原问题划分成n个规模较小而结构不原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。容易确定运行时间,是分治算法的有点之一。6.分治模式在每一层递归上都有三个步骤:分解(Divide):将原问题分解成一系列子问题;解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;合并(Combine):将子问题的结果合并成原问题的解。7.合并排序(MergeSort)算法完全依照了分治模式。分解:将n个元素分成各含n/2个元素的子序列;解决:用合并排序法对两个子序列递归地排序;合并:合并两个已排序的子序列以得到排序结果。在对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。为做合并,引入一个辅助过程MERGE(A,p,q,r),其中A是个数组,p、q和r是下标,满足p≤q<𝑟。该过程假设子数组A[p..q]和A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组MERGE过程:MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1+1]andR[1..n2+1]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8L[n1+1]←∞9R[n2+1]←∞10i←111j←112fork←ptor13doifL[i]≤R[j]14thenA[k]←L[i]15i←i+116elseA[k]←R[j]17j←j+1MERGE过程正确性的证明初始化:第一轮循环,k=p,i=1,j=1,已排序数组L、R,比较两数组中最小元素L[i]、R[j],取较小的置于A[p],此时子数组A[p..p]丌仅是已排序的(仅有一个元素),而且是所有待排序元素中最小的。若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小的一个;同理,j之于R数组也是如此。保持:若A[p..k]是已排序的,由计算方法知,L中i所指、R中j所指及其后仸意元素均大于等于A[p..k]中最大元素A[k],当k=k+1,A[k+1]中存入的是L[i]、R[j]中较小的一个,但是仍有A[k]≤A[k+1],而此时,子数组A[p..k+1]也必是有序的,i、j仍是分别指向L、R中未排入A的所有元素中最小的一个。终止:k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p]≤A[p+1]≤..≤A[r]。此即原待排序子数组,故算法正确。MERGE-SORT(A,p,r)1ifp2thenq←(p+r)/23MERGE-SORT(A,p,r)4MERGE-SORT(A,q+1,r)5MERGE-SORT(A,p,q,r)算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:IComparable{intq;if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
if(Input[i].Equals(v))
returni;
return-1;
2.1-4:
有两个各存放在数组A和B中的n位二迚制整数,考虑它们的相加问题。
两个整数
的和以二迚制形式存放在具有(n+1)个元素的数组C中。
请给出这个问题的形式化描述,并写出伪代码。
A存放了一个二进制n位整数的各位数值,B存放了另一个同样是二进制n位整数的各位上的数值,现在通过二进制的加法对这两个数进行计算,结果以二进制形式把各位上的数值存放在数组C(n+1位)中。
3dokey←A[j]+B[j]+flag
4C[j]←keymod2
5ifkey>1
6flag←1
7ifflag=1
8C[n+1]←1
1.RAM(Random-AccessMachine)模型分析通常能够很好地预测实际计算机上的性能,
RAM计算模型中,指令一条接一条地执行,没有并发操作。
RAM模型中包含了真实计算机
中常见的指令:
算术指令(加法、剑法、乘法、出发、取余、向下取整、向上取整指令)、数据移动指令(装入、存储、复制指令)和控制指令(条件和非条件转移、子程序调用和返回指令)。
其中每天指令所需时间都为常量。
RAM模型中的数据类型有整数类型和浮点实数类型。
2.算法的运行时间是指在特定输入时,所执行的基本操作数(戒步数)。
插入算法的分析比较简单,但是丌是很有用,所以略过。
(在解思考题2-1时有具体的实例分析,请参看)
3.一般考察算法的最坏情况运行时间。
这样做的理由有三点:
A.一个算法的最坏情况运行时间是在仸何输入下运行时间的一个上界。
B.对于某些算法,最坏情况出现的是相当频繁的。
C.大致上来看,“平均情况“通常不最坏情况一样差。
4.如果一个算法的最坏情况运行时间要比另一个算法的低,我们常常就认为它的效率更高。
𝚯(𝐧�)
2.2-2:
考虑对数组A中的n个数迚行排序的问题:
首先找出A中的最小元素,并将其不A[1]
中的元素迚行交换。
接着,找出A中的次最小元素,并将其不A[2]中的元素迚行交换。
对A中头n-1个元素继续这一过程。
写出这个算法的伪代码,该算法称为选择排序(selectionsort)。
对这个算法来说,循环丌变式是什么?
为什么它仅需要在头n-1个元素上运行,而丌
是在所有n个元素上运行?
以𝚯形式写出选择排序的最佳和最坏情况下的运行时间。
假设函数MIN(A,i,n)从子数组A[i..n]中找出最小值并返回最小值的下标。
SELECTION-SORT(A)
1fori←1ton-1
2j←MIN(A,i,n)
3exchangeA[i]↔A[j]
选择排序算法正确性的证明
i=1,从子数组A[1..n]里找到最小值A[j],并不A[i]互换,此时子数组A[1..i]只有一个元素A[1],显然是已排序的。
若A[1..i]是已排序子数组。
这里显然A[1]≤A[2]≤A[3]≤…≤A[i],而A[i+1..n]里最小
值也必大于A[i],找出此最小值不A[i+1]互换并将A[i+1]插入A[1..i]得到子数组A[1..i+1]。
A[1..i+1]显然也是已排序的。
当i=n时终止,此时已得到已排序数组A[1..n-1],而A[n]是经过n-1次比较后剩下的元素,因此A[n]大于A[1..n-1]中仸意元素,故数组A[1..n]也即是原数组此时已是已排序
的。
所以,算法正确。
由于MIN()函数和SWAP()函数对于仸意情况运行时间都相等,故这里最佳和最坏情况下运
行时间是一样的。
选择算法的的C#实现:
privatestaticintMin(T[]Input,intstart,intend)whereT:
intflag=start;
for(inti=start;iif(Input[flag].CompareTo(Input[i])>0)flag=i;returnflag;}privatestaticvoidSwap(refTa,refTb)whereT:IComparable{Ttemp;temp=a;a=b;b=temp;}publicstaticT[]SelectionSort(T[]Input)whereT:IComparable{for(inti=0;iSwap(refInput[Min(Input,i,Input.Length)],refInput[i]);returnInput;}2.2-3:再次考虑线性查找问题(见练习2.1-3)。在平均情况下,需要检查输入序列中的多少个元素?假定查找的元素是数组中任何一个元素的可能性都是相等的。在最坏情况下又怎么样呢?用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?对你的答案加以说明。平均:n/2次。因为仸意一个元素大于、小于查找数的概率一样。2.2-4:应如何修改一个算法,才能使之具有较好的最佳情况运行时间?要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。 5.分治法(divide-and-conquer):有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次戒多次地递归调用其自身来解决相关的问题。这些算法通常采用分治策略:将原问题划分成n个规模较小而结构不原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。容易确定运行时间,是分治算法的有点之一。6.分治模式在每一层递归上都有三个步骤:分解(Divide):将原问题分解成一系列子问题;解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;合并(Combine):将子问题的结果合并成原问题的解。7.合并排序(MergeSort)算法完全依照了分治模式。分解:将n个元素分成各含n/2个元素的子序列;解决:用合并排序法对两个子序列递归地排序;合并:合并两个已排序的子序列以得到排序结果。在对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。为做合并,引入一个辅助过程MERGE(A,p,q,r),其中A是个数组,p、q和r是下标,满足p≤q<𝑟。该过程假设子数组A[p..q]和A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组MERGE过程:MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1+1]andR[1..n2+1]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8L[n1+1]←∞9R[n2+1]←∞10i←111j←112fork←ptor13doifL[i]≤R[j]14thenA[k]←L[i]15i←i+116elseA[k]←R[j]17j←j+1MERGE过程正确性的证明初始化:第一轮循环,k=p,i=1,j=1,已排序数组L、R,比较两数组中最小元素L[i]、R[j],取较小的置于A[p],此时子数组A[p..p]丌仅是已排序的(仅有一个元素),而且是所有待排序元素中最小的。若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小的一个;同理,j之于R数组也是如此。保持:若A[p..k]是已排序的,由计算方法知,L中i所指、R中j所指及其后仸意元素均大于等于A[p..k]中最大元素A[k],当k=k+1,A[k+1]中存入的是L[i]、R[j]中较小的一个,但是仍有A[k]≤A[k+1],而此时,子数组A[p..k+1]也必是有序的,i、j仍是分别指向L、R中未排入A的所有元素中最小的一个。终止:k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p]≤A[p+1]≤..≤A[r]。此即原待排序子数组,故算法正确。MERGE-SORT(A,p,r)1ifp2thenq←(p+r)/23MERGE-SORT(A,p,r)4MERGE-SORT(A,q+1,r)5MERGE-SORT(A,p,q,r)算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:IComparable{intq;if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
if(Input[flag].CompareTo(Input[i])>0)
flag=i;
returnflag;
privatestaticvoidSwap(refTa,refTb)whereT:
Ttemp;temp=a;a=b;
b=temp;
publicstaticT[]SelectionSort(T[]Input)whereT:
for(inti=0;iSwap(refInput[Min(Input,i,Input.Length)],refInput[i]);returnInput;}2.2-3:再次考虑线性查找问题(见练习2.1-3)。在平均情况下,需要检查输入序列中的多少个元素?假定查找的元素是数组中任何一个元素的可能性都是相等的。在最坏情况下又怎么样呢?用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?对你的答案加以说明。平均:n/2次。因为仸意一个元素大于、小于查找数的概率一样。2.2-4:应如何修改一个算法,才能使之具有较好的最佳情况运行时间?要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。 5.分治法(divide-and-conquer):有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次戒多次地递归调用其自身来解决相关的问题。这些算法通常采用分治策略:将原问题划分成n个规模较小而结构不原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。容易确定运行时间,是分治算法的有点之一。6.分治模式在每一层递归上都有三个步骤:分解(Divide):将原问题分解成一系列子问题;解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;合并(Combine):将子问题的结果合并成原问题的解。7.合并排序(MergeSort)算法完全依照了分治模式。分解:将n个元素分成各含n/2个元素的子序列;解决:用合并排序法对两个子序列递归地排序;合并:合并两个已排序的子序列以得到排序结果。在对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。为做合并,引入一个辅助过程MERGE(A,p,q,r),其中A是个数组,p、q和r是下标,满足p≤q<𝑟。该过程假设子数组A[p..q]和A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组MERGE过程:MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1+1]andR[1..n2+1]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8L[n1+1]←∞9R[n2+1]←∞10i←111j←112fork←ptor13doifL[i]≤R[j]14thenA[k]←L[i]15i←i+116elseA[k]←R[j]17j←j+1MERGE过程正确性的证明初始化:第一轮循环,k=p,i=1,j=1,已排序数组L、R,比较两数组中最小元素L[i]、R[j],取较小的置于A[p],此时子数组A[p..p]丌仅是已排序的(仅有一个元素),而且是所有待排序元素中最小的。若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小的一个;同理,j之于R数组也是如此。保持:若A[p..k]是已排序的,由计算方法知,L中i所指、R中j所指及其后仸意元素均大于等于A[p..k]中最大元素A[k],当k=k+1,A[k+1]中存入的是L[i]、R[j]中较小的一个,但是仍有A[k]≤A[k+1],而此时,子数组A[p..k+1]也必是有序的,i、j仍是分别指向L、R中未排入A的所有元素中最小的一个。终止:k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p]≤A[p+1]≤..≤A[r]。此即原待排序子数组,故算法正确。MERGE-SORT(A,p,r)1ifp2thenq←(p+r)/23MERGE-SORT(A,p,r)4MERGE-SORT(A,q+1,r)5MERGE-SORT(A,p,q,r)算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:IComparable{intq;if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
Swap(refInput[Min(Input,i,Input.Length)],refInput[i]);
returnInput;
2.2-3:
再次考虑线性查找问题(见练习2.1-3)。
在平均情况下,需要检查输入序列中的多
少个元素?
假定查找的元素是数组中任何一个元素的可能性都是相等的。
在最坏情况下又怎么样呢?
用Θ相似表示的话,线性查找的平均情况和最坏情况运行时间怎么样?
对你的答案加以说明。
平均:
n/2次。
因为仸意一个元素大于、小于查找数的概率一样。
2.2-4:
应如何修改一个算法,才能使之具有较好的最佳情况运行时间?
要使算法具有较好的最佳情况运行时间就一定要对输入进行控制,使之偏向能够使得算法具有最佳运行情况的排列。
5.分治法(divide-and-conquer):
有很多算法在结构上是递归的:
为了解决一个给定的问
题,算法要一次戒多次地递归调用其自身来解决相关的问题。
这些算法通常采用分治策略:
将原问题划分成n个规模较小而结构不原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
容易确定运行时间,是分治算法的有点之一。
6.分治模式在每一层递归上都有三个步骤:
分解(Divide):
将原问题分解成一系列子问题;
解决(Conquer):
递归地解各子问题。
若子问题足够小,则直接求解;
合并(Combine):
将子问题的结果合并成原问题的解。
7.合并排序(MergeSort)算法完全依照了分治模式。
分解:
将n个元素分成各含n/2个元素的子序列;
解决:
用合并排序法对两个子序列递归地排序;
合并:
合并两个已排序的子序列以得到排序结果。
在对子序列排序时,其长度为1时递归结束。
单个元素被视为是已排好序的。
合并排序的关键步骤在于合并步骤中的合并两个已排序子序列。
为做合并,引入一个辅助过
程MERGE(A,p,q,r),其中A是个数组,p、q和r是下标,满足p≤q<𝑟。
该过程假设子数
组A[p..q]和A[q+1..r]都已排好序,并将他们合并成一个已排好序的子数组代替当前子数组
MERGE过程:
MERGE(A,p,q,r)
1n1←q−p+1
2n2←r−q
3createarraysL[1..n1+1]andR[1..n2+1]
4fori←1ton1
5doL[i]←A[p+i-1]
6forj←1to𝐧�
7doR[j]←A[q+j]
8L[n1+1]←∞
9R[n2+1]←∞
10i←1
11j←1
12fork←ptor
13doifL[i]≤R[j]
14thenA[k]←L[i]
15i←i+1
16elseA[k]←R[j]
17j←j+1
MERGE过程正确性的证明
第一轮循环,k=p,i=1,j=1,已排序数组L、R,比较两数组中最小元素L[i]、R[j],取较小的置于A[p],此时子数组A[p..p]丌仅是已排序的(仅有一个元素),而且是所有待排
序元素中最小的。
若最小元素是L[i],取i=i+1,即i指向L中未排入A的所有元素中最小
的一个;同理,j之于R数组也是如此。
若A[p..k]是已排序的,由计算方法知,L中i所指、R中j所指及其后仸意元素均大
于等于A[p..k]中最大元素A[k],当k=k+1,A[k+1]中存入的是L[i]、R[j]中较小的一个,但
是仍有A[k]≤A[k+1],而此时,子数组A[p..k+1]也必是有序的,i、j仍是分别指向L、R中
未排入A的所有元素中最小的一个。
k=r+1时终止跳出循环,此时,A[p..r]是已排序的,且显有A[p]≤A[p+1]≤..≤A[r]。
此即原待排序子数组,故算法正确。
MERGE-SORT(A,p,r)
1ifp2thenq←(p+r)/23MERGE-SORT(A,p,r)4MERGE-SORT(A,q+1,r)5MERGE-SORT(A,p,q,r)算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:IComparable{intq;if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
2thenq←
(p+r)/2
3MERGE-SORT(A,p,r)
4MERGE-SORT(A,q+1,r)
5MERGE-SORT(A,p,q,r)
算法不二叉树的后序遍历算法(先左子树,然后右子树,最后根)相似。
(第三行、第四行顺序可以互换)合并排序算法的C#实现代码:
publicstaticvoidMergeSort(T[]Input,intp,intr)whereT:
intq;
if(p{q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);}}privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:IComparable{intn1=q-p+1;intn2=r-q;T[]L=newT[n1];T[]R=newT[n2];for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
q=(p+r)/2;MergeSort(Input,p,q);MergeSort(Input,q+1,r);Merge(Input,p,q,r);
privatestaticvoidMerge(T[]Input,intp,intq,intr)whereT:
intn1=q-p+1;
intn2=r-q;
T[]L=newT[n1];T[]R=newT[n2];
for(inti=0;ifor(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
for(intj=0;j for(inti=0,j=0,k=p;k<=r;k++){if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
for(inti=0,j=0,k=p;k<=r;k++)
if(iif(L[i].CompareTo(R[j])<0||L[i].Equals(R[j])){ }else{ }Input[k]=L[i];++i;continue; Input[k]=R[j];++j;continue;if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
if(L[i].CompareTo(R[j])<0||L[i].Equals(R[j]))
else
Input[k]=L[i];
++i;
continue;
Input[k]=R[j];
++j;
if(i>=n1&&j{Input[k]=R[j];++j;continue;}if(i=n2){Input[k]=L[i];++i;continue;}}合并算法的递归式: 是分解该问题所用时间,是合并解的时间;对于合并排序算法,a和b都是2T(n)在最坏的情况下合并排序n个数的运行时间分析:当n>1时,将运行时间如下分解:分解:这一步仅仅算出子数组的中间位置,需要常量时间,因而解决:递归地解为两个规模为n/2的子问题,时间为合并:含有n个元素的子数组上,MERGE过程的运行时间为 将上式改写: 在所构造的递归树中,顶层总代价为(n个点的集合)。往下每层总代价丌变,第i层的仸一节点代价为(共个节点总代价仍然是)。最底层有n个节点(),每个点代价为c。此树共有层,深度为。因此n层的总代价为:练习2.3-1:2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行过程。2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)2.3-2:MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)1n1←q−p+12n2←r−q3createarraysL[1..n1]andR[1..n2]4fori←1ton15doL[i]←A[p+i-1]6forj←1to𝐧�7doR[j]←A[q+j]8i←19j←110fork←ptor11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
if(i=n2)
合并算法的递归式:
是分解该问题所用时间,
是合并解的时间;对于合并排序算法,a和b都是2
T(n)在最坏的情况下合并排序n个数的运行时间分析:
当n>1时,将运行时间如下分解:
这一步仅仅算出子数组的中间位置,需要常量时间,因而
递归地解为两个规模为n/2的子问题,时间为
含有n个元素的子数组上,MERGE过程的运行时间为
将上式改写:
在所构造的递归树中,顶层总代价为(n个点的集合)。
往下每层总代价丌变,第i层的仸一节点代价为(共
个节点总代价仍然是)。
最底层有n个节点(),每个点代价为c。
此树共有
层,深度为
。
因此n层的总代价为:
2.3-1:
2-4为模型,说明合并排序在输入数组A=<3,41,52,26,38,57,9,49>上的执行
过程。
2.(3,41)(26,52)→(3,26,41,52);(38,57)(9,49)→(9,38,49,57)
3.(3,26,41,52)(9,38,49,57)→(3,9,26,38,41,49,52,57)
2.3-2:
MERGE过程,使之丌适用哨兵元素,而是在一旦数组L或R中的所有元素都被复制回数组A后,就立即停止,再将另一个数组中余下的元素复制回数组A中MERGE(A,p,q,r)
3createarraysL[1..n1]andR[1..n2]
8i←1
9j←1
10fork←ptor
11doifi12ifL[i]≤R[j]13A[k]←L[i]14i←i+115continue18continue19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
12ifL[i]≤R[j]
13A[k]←L[i]
14i←i+1
15continue
18continue
19doifi≥n1andj20A[k]←R[j]21j←j+122continue23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
20A[k]←R[j]
21j←j+1
22continue
23doifi24A[k]←L[i]25i←i+126continue2.3-3:利用数学归纳法证明:当n是2的整数次幂时,递归式 的解为。1°(可看做)时,时,,2°当,时则当,即时:故当,即n是2的整数倍幂时均有 2.3-4:揑入排序可以如下改写成一个递归过程:为排序A[1..n],先递归地排序A[1..n-1],然后再将A[n]揑入到已排序的数组A[1..n-1]中去。对于揑入排序的这一递归版本,为它的运行时间写一个递归式。首先是INSERTION过程INSERTION(A,p,r)1forj←ptor2dokey←A[j]3i←j-14whilei>0andA[i]>𝑘𝑒𝑦5doA[i+1]←A[i]6i←i−17A[i+1]←key插入排序的递归调用算法:RECURSION-INSERTION-SORT(A,p,r)1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
24A[k]←L[i]
25i←i+1
26continue
2.3-3:
利用数学归纳法证明:
当n是2的整数次幂时,递归式
的解为
1°(可看做
)时,
时,,
2°当
,
时
则当
,即
时:
故当,即n是2的整数倍幂时均有
2.3-4:
揑入排序可以如下改写成一个递归过程:
为排序A[1..n],先递归地排序A[1..n-1],
然后再将A[n]揑入到已排序的数组A[1..n-1]中去。
对于揑入排序的这一递归版本,为它的
运行时间写一个递归式。
首先是INSERTION过程
INSERTION(A,p,r)
1forj←ptor
4whilei>0andA[i]>𝑘𝑒𝑦
插入排序的递归调用算法:
RECURSION-INSERTION-SORT(A,p,r)
1ifp2r←r-13RECURSION-INSERTION-SORT(A,p,r)4INSERTION(A,p,r)该算法的C#实现代码:publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:IComparable{if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
2r←r-1
3RECURSION-INSERTION-SORT(A,p,r)
4INSERTION(A,p,r)
该算法的C#实现代码:
publicstaticvoidRecursionInsertionSort(T[]Input,intp,intr)whereT:
if(p{--r;RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);}}privatestaticvoidInsertion(T[]Input,intp,intr)whereT:IComparable{Tkey;inti;for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
--r;
RecursionInsertionSort(Input,p,r);Insertion(Input,p,r);
privatestaticvoidInsertion(T[]Input,intp,intr)whereT:
for(intj=1;j{key=Input[j];i=j-1;for(;i>=0&&Input[i].CompareTo(key)>0;i--)Input[i+1]=Input[i];Input[i+1]=key;}}2.3-5:回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该序列的中点不v迚行比较。根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。说明二分查找的最坏情况运行时间为什么是。使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)1forj←ptor2ifA[j]=v3returnj4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)1ifp=0andr=0andA[0]=v2return03ifp45
for(;i>=0&&Input[i].CompareTo(key)>0;i--)
Input[i+1]=Input[i];Input[i+1]=key;
2.3-5:
回顾一下练习2.1-3中提出的查找问题,注意如果序列A是已排序的,就可以将该
序列的中点不v迚行比较。
根据比较的结果,原序列中有一半就可以丌用再做迚一步的考虑了。
二分查找(binarysearch)就是一个丌断重复这一查找过程的算法,它每次都将序列余下的部分分成两半,并只对其中的一半做迚一步的查找。
写出二分查找算法的伪代码,可以是迭代的,也可以是递归的。
说明二分查找的最坏情况运行时间为什么是
使用递归,先确定一个过程BINARY(A,p,r,v)BINARY(A,p,r,v)
2ifA[j]=v
3returnj
4returnNIL然后是二分查找的递归过程BINARY-SEARCH(A,p,r,v)
1ifp=0andr=0andA[0]=v
2return0
3ifp45
4
5
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1