第一章包含在第一部分里面,第一部分总共有五章:
- 第一章 开篇
- 第二章 啊哈!算法
- 第三章 数据决定程序结构
- 第四章 编写正确的程序
- 第五章 编程小事
第一章 开篇
这一章揭示了本书的中心思想:对实例研究的深入思考不仅很有趣,而且可以获得实际的益处。
1.1 一次友好的对话
一位程序员问Jon:怎样给一个磁盘文件排序?Jon就回答他,归并排序,并且给他介绍了一堆资料,并估计”他要一周时间来完成和测试代码“。然而,那位程序员的踌躇却产生了以下的对话:
”
Jon:为什么非要自己编写排序程序?为什么不用系统提供的排序功能呢?
程:我需要在一个大型系统中排序。由于不明的技术原因,我不能使用系统中的文件排序程序。
Jon:需要排序的内容是什么?文件中有多少记录?每条记录的格式是什么?
程:文件最多包含1千万条记录,每天记录都是7位的整数。
Jon:等一下,既然文件这么小,何必非要在磁盘上进行排序呢?为什么不再内存里面进行排序?
程:尽管机器里有许多M的内存,但是排序功能只是大系统中的一部分,所以,估计到时候只有1MB的内存可以使用。
Jon:你还能告诉我其他一些与记录相关的信息么?
程:每天记录都是7位数的正整数,再无其他相关数据。每条数据最多只出现一次。
“
“这些对话让问题更加明确了”,作者说,“我错就错在马上回答了这个问题”。如果那位程序员没有继续和作者聊天,可能这些详细的需求可能就不会出现。那么,以后当有人问你问题的时候,如果你真的想回答他,你最好能把他的“需求”弄清楚再回答,而不是直接告诉他“用XXX就好了”或者“Google一下,网上很多的”。
1.3 程序设计
排序,一个很常见的功能,有各种各样的算法来支持,但是文中提到了一些特殊的条件“在1M左右内存中,排序1千万个7位整数,每个整数都不重复”。如果用int数组来保存数据,那么只能保存250000个数。那么排序1千万个数,需要读写40次文件才能完成。例如:第一次读文件,用数组保存下0至250000之间的数,然后排序,写入输出文件,然后进行第二次读取,保存250001至500000之间的数,然后再排序,追加进输出文件中。。。。。
这是很容易想到的方法,是似乎有点麻烦,需要40次读取文件。有没有更加简单的方法呢?
1.4 实现概要
如果你仔细观察上面的“特殊条件”,那么在结合下面的方法,你就能很轻松的解决这个问题。
位图(位向量):用一个长字符串中的元素来表示一个集合中的第几位是否满足某种属性。例如有一个集合{1,2,4,5},就可以用{1,1,0,1,1}来表示。0表示该下标元素不存在,1表示该下表元素存在。在这个例子中,1、2、4、5均存在,而3不存在,所以集合中第三位为0,其他均为1(说简单一点,就是位图中第几位就表示数值几,然后用0,1来表示存在或不存在)。最后可以直接写成11011。
在这个问题中,每个整数不重复,范围是1到1千万,如果用1个二进制来表示1个数,为了表示1千万个数,那么就需要1千万个二进制 = 1百25万个字节 = 1.25M。在实现时,不同语言关于二进制位的实现不同(C++、Java中都有BitSet),这个就看各位了。
有了位图,剩下的就简单了,只要读入文件,比如第一次读到100,那么就把位图数组中的第100个元素设置为1(bit[100] = 1),表示100出现了,其他的以此类推。
最后,遍历位图数组,从0到1千万,如果数组中某位元素为1,那么输出该位(if bit[i] == 1 print i)
你看,这样不就实现了排序么?
1.5 原理
作者最后总结到
- 对小问题的分析有时候可以得到明显的益处。
- 正确的问题:明确了问题,这场战役就成功了90%。
- 多趟算法:假设现在内存限定1M,那么如何利用位图解决这个问题?(提示:将位图和40趟算法结合一下)
- 空间-时间折中与双赢:如果你选择对了数据结构或者设计方法,那么,你的程序可能在空间和时间上都得到优化。
- 简单的设计:“设计者确定某个设计达到完美的标准不是不能再增加任何东西,二是不能在减少任何东西”。简单的程序通常比具有相同功能的复杂程序更可靠、更安全、更健壮、更高效,而且更易于实现和维护。