《编程珠玑》总结Word文档格式.docx
《《编程珠玑》总结Word文档格式.docx》由会员分享,可在线阅读,更多相关《《编程珠玑》总结Word文档格式.docx(15页珍藏版)》请在冰豆网上搜索。
topsopsttopsopsttops
Sign程序假定单词的长度不超过100个字母,并且输入文件只包含小写字母和换行符
intcharcomp(char*x,char*y)
{
return*x-*y;
}
#defineWORDMAX100
intmain(void)
charword[WORDMAX],sig[WORDMAX];
while(scanf("
%s"
word)!
=EOF){
strcpy(sig,word);
qsort(sig,strlen(sig),sizeof(char),charcomp);
printf("
%s%s\n"
sig,word);
}
return0;
qsort函数的参数是待排序的数组,数组长度,每个排序项的字节数以及比较两个项的函数名称,这里是比较单词内的字符。
系统sort程序将具有相同签名的单词全部整理到一起
squash程序将它们输出在单个行中。
charword[WORDMAX],sig[WORDMAX],oldsig[WORDMAX];
intlinenum=0;
strcpy(oldsig,"
"
);
%s%s"
sig,word)!
if(strcmp(oldsig,sig)!
=0&
&
linenum>
0)
\n"
strcpy(oldsig,sig);
linenum++;
%s"
word);
(2)
第2章:
(P14)
手摇法:
(3)
第8章:
(P72)
问题的输入是一个具有n个浮点数字的向量x,其输出是在输入的任何相邻子向量中找出的最大和。
例如,如果输入向量包含下面10个元素:
31
-41
59
26
-53
58
97
-93
-23
84
↑↑
26
那么该程序将返回x[2...6]的总和。
所有数字都是正数时问题很简单,最大的子向量就是整个输入向量。
为了使问题定义更加完整,我们认为当所有的输入都是负数时,最大总和子向量是空向量,空向量的总和为0.
完成该任务的程序显然迭代了所有满足0≤i≤j<
n的i和j整数对,对每个整数对,它都要计算x[i...j]的总和,并检查总和是否大于迄今为止的最大总和。
算法1的伪代码如下:
О(n3)
两个二次算法:
О(n2)
第一个二次算法注意到x[i...j]中的总和与前面已计算的x[i...j-1]的总和的密切相关,从而快速计算总和,利用那种关系导致了算法2的产生:
另一个备选的二次算法是通过访问在外部循环执行之前就已构建的数据结构的方式在内部循环中计算总和。
cumarr的第i个元素包含x[0...j]中的各个值得累加和,所以x[i...j]中的各个值的总和可以通过计算cumarr[j]-cumarr[i-1]得到,这就得到了以下的算法:
分治算法:
О(nlogn)
要解决规模为n的问题,可递归解决两个规模近似为n/2的子问题,然后将它们的答案进行合并以得到整个问题的答案。
创建两个大小相当的子向量,称之为a和b
a
b
递归地找出a和b中元素和最大的子向量,称之为ma和mb
ma
mb
最大值要么整个在a中,要么整个在b中,或跨越a和b之间的边界,我们将跨越边界的最大值成为mc
mc
剩下需要描述的就是我们该如何处理小向量以及如何计算mc前者比较简单,只有一个人元素的向量的最大值就是该向量中的唯一值(如果是负数就是0),零向量的最大值定义为0.要计算mc,我们观察到,其左边是从边界开始到达a的最大子向量,右边在b中的情况与此类似。
由此得到算法3的代码:
算法3最初通过下面的方式调用:
扫描算法:
О(n)
从最左端(x[0])开始,一直扫描到最右端,(x[n-1]),记下所碰到过的最大总和子向量。
最大值最初是0.假设我们已经解决了针对于x[0...i-1]的问题,我们如何将其扩展为包含x[i]的问题呢?
我们使用类似分治算法中的道理:
前i个元素中,最大总和子数组要么在i-1个元素中(我们将它存储在maxsofar中),要么截止到位置i(我们将它存储在maxendinghere中)。
maxsofar
maxendinghere
使用类似算法3的代码重新开始计算maxendinghere得到另一个二次算法。
我们可以使用导出算法2的技术避开这一点:
我们不计算截止于位置i的最大子向量,反而使用截止于位置i-1的最大子向量,这就导出了算法4:
理解这个程序的关键就是变量maxendinghere,在该循环的第一个赋值语句前,maxendinghere包含了截止于位置i-1的最大子向量的值;
赋值语句修改它以包含截止于位置i的最大子向量的值。
只要这样做能够保持其为正值,该语句将向它增加一个值x[i];
当它变为负值时,就将它重新设为0(因为截止于i的最大子向量现在是空向量了)。
(4)
第9章(P85)
顺序查找:
v1.0
v2.0
内部循环有两个测试:
第一个测试i是否在数组尾了,第二个测试x[i]是否是预期的元素。
我们可以在该数组的末尾加上一个标记值,从而使用第二个测试来替换第一个测试:
上述代码假设该数组已经分配,这样x[n]可能会被临时覆盖掉。
保存x[n]以及在查找之后对其进行恢复都要很小心,这在大部分场合是不必要的所以在下一个版本会被删掉。
v3.0
现在最内层循环只包含一个加法,一个数组访问以及一个测试了,我们最终的顺序查找将循环展开8次,删除了加法;
进一步展开并不会加速其运行速度。
(5)
第11章(P109)
插入排序:
假设现在要将数组x[n]按增序排列,那么首先将第一个元素作为已排列子数组x[0...0],然后插入元素x[1]...x[n-1],如下面的伪码所示:
下面四行体现了该算法在一个具有4个元素的数组上的整个应用过程。
“|”代表变量i,它左边的元素已经排过序,而它右边的元素则是按原始顺序排列的,还没有排序。
移动通过一个从左到右的循环实现,该循环使用变量j跟踪被移动的元素。
只要某个元素有前趋元素(也就是j>
0)并且没有到达其最终位置(即该元素小于它的前趋元素),循环就交换该元素和它的前趋元素。
因此,isort1如下所示:
使用下面的代码替换swap函数,得到isort2,运行效率提高了:
这次改动又为下一步的加速提供了思路,由于在上面的函数体中,不断给变量t赋予相同的值(x[i]中的初始值),因此,可以将给t赋值的语句和将t值赋给其他变量的语句移出循环,并修改比较语句,从而得到isort3:
只要t小于数组值,该段代码就将元素右移一个位置,并最终将t移到它的正确位置。
(6)
第11章(P110)
快速排序:
V1.0
排序数组时,将数组划分成两个较小的部分,然后递归地排序它们,例如,假设现在要排序一个具有8个元素的数组:
55
41
53
93
07
在第一个元素55处划分,这样所有小于55的元素都位于该元素的左边,所有大于55的元素都位于它的右边。
<
55>
3
如果分别递归排序子数组0到2和子数组4到7,那么整个数组就排序好了。
下面使用下标l和u分别表示数组活动部分的上下界,当数组元素小于2时,终止递归,
代码如下所示:
将数组根据值t进行划分,重新组合x[a...b],计算下标m(“中间值”的下标),这样所有小于t的元素在m的一端,而所有大于t的元素在m的另一端。
下面通过一个简单的for循环实现该任务,for循环从左到右扫描数组,使用变量i和m指向数组x中的不定式。
t
≥t
?
amib
代码在检查第i个元素时必须考虑两种情况。
如果x[i]≥t,那么不定式仍然为真,另一方面,当x[i]≤t时,可以通过增加m来重新获得不定式(这就需要指向元素的新位置),然后交换x[i]和x[m],完整的划分代码如下:
我们根据t=x[l]划分数组x[l..u],这样a=l+1,b=u。
因此,划分循环的不定式如下所示:
lmiu
循环终止得到:
<t
lmb
然后交换x[l]和x[m]得到:
t
接着可以使用参数(l,m-1)和(m+1,u)两次递归调用该函数。
最终得到了第一个快排qsort1的代码,假设使用函数qsort1(0,n-1)排序数组x[n].
V2.0
考虑一种极端情况:
n个元素都相等的数组。
n-1次划分每次都需要О(n)时间去掉单个元素。
通过使用下面的循环不定式,谨慎处理两端的划分可以避免这个问题。
≤t
?
liju
下标i和j的初始值为要划分的数组的两个极值。
主循环中有两个内部循环,第一个内部循环从左边开始,将i移过小元素,遇到大元素停止。
第二个内循环从右边开始,将j移到大元素之后,直到遇到一个较小的元素。
然后,主循环判断这两个下标,看是否交叉,然后交换两个值。
(遇到相同元素停止扫描并交换元素)
V3.0
如果输入的数组早就按照递增顺序排列,依然根据数组的第一个元素进行划分,需要时间О(n2)。
这样的话随机选择一个划分元素要比这样做好的多,我们通过x[l]与x[l..u]中的一个随即项交换来实现:
randint返回的值在范围[l,u]之间:
这个快排使用大量的时间来排序一个非常小的子数组,使用插入排序速度要快的多。
-->
当在小的子数组上调用快排时(也就是l和u非常接近时),不需要执行任何操作。
通过将函数中的第一个i