《编程珠玑》读书笔记 part3

第三部分 应用


第11章 排序


排序算法的应用场合有如下一些:用户需求、收集相同的项、服务于类似于二分搜索等有需求的算法;


插入排序的普通做法是把待排序的元素与左侧的相邻元素比较,并依据情况考虑是否要交换,如果交换再与下一个元素比较,决定是否交换;
频繁交换显然效率较低,所以通常采用的高效做法是用一个临时位置保存待排序的元素,然后左侧每一个比该元素大的元素都向右平移一个位置,直到找到合适位置把待排序的元素放进去。
排序算法的一个优点是稳定——相同关键字的元素在排序前后相对位置不变,这一点是快速排序和堆排序都不具备的;


快速排序的普通方式是,选择最左侧的元素t作为标准,用一个指针扫描数组的剩余部分,把元素分为小于等于t和大于t两部分,再递归地对这两部分进行排序。
优化1:针对全部元素都相等的数组效率仅为O(n^2)的问题,可以采用双向划分,即用两个指针分别从两侧扫描,得到大于等于t和小于等于t两部分;
优化2:针对有序数组效率为O(n^2)的问题,可以以随机元素而不是固定在最左侧位置的元素作为标准,这样可以在平均意义上提高效率
优化3:快排的时间常数比较大,对于小规模数组用插入排序更好,所以可以在递归到一个较小规模后,改用插入排序完成。
--------------------------------


第12章 取样问题


这一章涉及了产生随机数的问题,我们假定有random库函数的帮助,以实现[0,Max]区间内均匀分布随机整数的产生,配合相乘、取模运算,可以得到任意区间(i,j)内的随机数。
问题:给定正整数m, n(m≤n), 产生0,1,2, ..., n-1 范围内的m个随机数。
方法1:遍历所有数字,对于每次以一定概率保留当前的数字,具体而言,当当下需要从剩余的r个数字中选出s个时,保留当前数字的概率设置为s/r(这个概率可以用取余运算模拟), 这样所有数字都以等可能被保留,且最终刚好剩余s个整数。缺点:所需时间与n成正比,通常m 远小于 n,显然不够划算;
方法2:利用random函数产生0, 1, 2, ..., n-1范围内的随机数,将其依次插入一个大小为m的集合S中,如果产生的随机数已经在S中就再产生一个。实现方法:利用C++中的set标准模板库。C++模板库每次插入可以在O(log m)时间内完成,遍历集合需要O(m),因此总时间为O(mlogm)
方法3:把前m个整数打乱:对于第i个整数,把它与( i, n-1 )范围内的一个随机整数交换,然后输出前m个整数。时间效率 O(n+mlogm),其中的n是用来初始化数组的,注意方法2中不需要这样一个显示的数组。(个人感觉可以直接产生随机数,不需要用显示的数组进行交换操作,这样效率更高吧)


实际操作中还需要考虑一些特别的情况,比方说当m接近n的时候,可以产生n-m个随机数,再把剩余的数字作为输出。


摘录:解决现有的问题是程序员任务的一部分,另一个也许更重要的部分是做好解决未来问题的准备。有时,这种准备包括听课或者读书(例如Knuth的书),不过更常见的情况是,程序员通过询问自己如何用不同的方法解决问题来得到提高。
--------------------------------


第13章 搜索


这一章继续前面产生随机数的例子,讨论set可以用什么方法实现,主要对比了数组和链表两种线性结构,另外还有对二分搜索数和用于整数搜索的位图结构。


在线性结构的讨论中有如下要点需要记住:
· 尽量分配具有m个结点的块,而不是每次插入时单独分配,因为存储分配的时间比大多数简单元算高两个数量级,次数越少越好;其次多次分配结点所需要的额外空间较多,会造成严重的空间浪费;
· 链表执行有序的插入操作看起来比数组(顺次访问,将比待插入元素大的元素后移一个位置)要快,但实际测试却是数组算法时间的两倍,造成这一现象的原因之一是链表中每个结点所需空间更大,导致可能超过缓存限制,其次数组在空间上是连续的,地址具有可预见性,而链表的结点在顺次访问时地址也可能是来回跳跃的。
--------------------------------


第14章 堆


介绍堆数据结构和它的两个最重要应用:堆排序、优先队列。堆排序采用最大堆结构,用于把一个乱序数组调整为递增数组,实现方法是数组的前一部分是无序的待调整的堆,后一部分是每次从前面选出的最大元依次构成的有序数组,算法先用O(nlogn)的时间建立堆,再用同样的量级的时间进行排序。堆排序即使在最坏情况下的执行效率也是O(nlogn)的,但它的时间常数较大,比较常用的场合是选出一个数组的top m元素,此时不需要完全排序。而如果需要排序,则经过优化的快排是最优的。


摘录:每个数据结构都可以从两方面看。从外部来看,它的规范说明了它做什么——队列通过insert和extract来维护元素序列。从内部来看,它的实现说明了它如何做——队列可以使用数组或链表来实现。
--------------------------------


第15章 字符串


· 在输出单词词频统计结果的过程中,可以使用C++的map模板,它可以直接建立单词(例如“the”)到数字(词频)的映射,而不需要用一个结构体数组去表示这个列表;
· hash散列表的大小通常去与待存储的元素总数最接近的质数;
· 散列法速度很快,但缺点是缺乏平衡树能提供的最坏情况性能保证,也不能支持其他涉及顺序的操作;
· 对于在一个很庞大的文档中寻找指定的词组之类的问题,常常需要扫描全文。但如果可以对文档进行预处理,则可以提高此类问题的效率。经典的“倒排索引”方法中,我们记录每个单词出现的位置,以单词为索引关键字,当我们需要搜索词组时,只需要去一系列位置的交集,并在其中做简单的搜索即可;
· 一种更高级的数据结构称为“后缀数组”,此数组的第i个元素是文本中的第i个字符到最后一个构成的字符串;整个数组就构成了文本的全部后缀。这样可以比较方便地实现一些和子串相关的功能,例如:寻找最长重复子串(将后缀数组按字典序排列,排序后所有相邻两个元素的共同前缀就是所有的重复子串),统计子串出现的频率等等;


==================================


推荐阅读书籍汇总:


· Michael Jackson, Software requirements and specifications(recommended in Ch1)
· Fred Brooks, 《人月神话》。作者Fred因在计算机体系结构,操作系统和软件工程方面里程碑性的贡献而获得1999年图灵奖,领导了OS/360操作系统的开发,并以此写成名著《人月神话》。(Ch2)
· Steve McConnell, 《代码大全》, 副标题为A practical handbook of software construction.(Ch3,5,9)
· Steve McConnell, Rapid development ( 快速软件开发)(Ch3)
· Steve McConnell, software project survival guide(软件项目生存指南)(Ch3)
     - 以上三本书覆盖了从开发有趣的小函数到管理大的软件项目的范围内所需的软件开发知识的大部分。
· David Gries, science of programming, 程序验证领域极佳的入门书籍。(Ch4)
· Kernighan, Pike, Practice of Programming(Ch5, 9)
· Andrew Appel,《现代编译原理:C语言描述》(Ch6)
· Butler Lampson, Hints for Computer System Design. (这是一篇文章)(Ch6)
· Darrell Huff, How to lie with Statistics;(Ch7)
· John Allen Paulos, 《数盲:数学无知者眼中的迷惘世界》
· Aho, Hopcroft, Ullman, Data structures and algorithms(数据结构与算法)(Ch8)
· Cormen, Leiserson, Tivest, Introduction to Algorithms(算法导论)(Ch8)
· Don Knuth, The art of computer programming(计算机程序设计艺术)(Ch11,12,13,14)
     - Don Knuth,因算法分析和编程语言设计领域的贡献或1974年图灵奖,KMP算法设计者之一,The art of computer programming的作者,TeX排版系统设计者。
· Robert Sedgewick, Algorithms, ( Algorithms in C/C++/Java) (Ch11)
· Polya,How to solve it.(Ch12)
------------------------------------


写读书笔记过程的一点点感悟:当你试图把认为自己已经理解的东西写下来时,常常会发现头脑中的想法有不严谨之处,有想当然之嫌,所以写下来有助于完善自己的知识结构。更重要的是有时候很多新的想法会在写作的过程中浮现出来,对于这一点,例子实在很多,没有一 一记录。基于这两点,我觉得,读书笔记真的是一个很有必要的过程——如果你想深入地理解一本书的话。

你可能感兴趣的:(《编程珠玑》读书笔记 part3)