编程珠玑译-Column 1:开篇

来源:互联网 发布:淘宝上卖视频自动发货 编辑:程序博客网 时间:2024/06/16 19:31

   这个程序的问题是简单的,“我该怎么样给你个磁盘文件做排序?”  在我告诉你我是怎么犯下我第一个错误之前,让我给你一个机会去做得比我还好。你会怎么回答?

1.1 一次友好的谈话

    我的错误就是回答了他的这个问题。为给了他一个关于怎么在磁盘上实行归并排序的缩略图草图。我建议他深入研究一下这个会让他遇见比一时的热情更少错误的算法文本 ——他更关心如何解决这个问题而不是深入他的学习。接着我告诉了他关于在一本著名的程序书上的这段磁盘排序程序。这个程序由大概200行代码,许多的函数组成;我估计应用并且测试好这段代码将会让程序员大概花费一周的时间。

    我觉得我已经解决了这个问题,但是他的犹豫让我回到了这个正确的轨道上。这个带着我的问题【斜体字】的谈话接下来就如接下来的这些。

    

    你为什么想要写出你自己的完全的排序算法呢??为什么不使用一个你的系统提供的呢??

    为需要这个在大型系统中间的算法,并且为了一个模糊的技术原因, 我不能够使用这个系统自带的文件排序程序。

    你到底想要排序什么呢?在文件中有多少记录?每一个记录的格式是啥?

    这个文件包括了大概一千万条记录;每一条记录都是一个十六进制的整数。

    等一下,如果这个文件是这么小,为什么要麻烦的去用磁盘呢?为什么不能就在主存缓冲区中排序呢?

    就算这个及其有着许多兆字节的主存缓冲区,但这个函数是整个大系统的一部分。我觉得我只能使用一个兆字节的大小在这个点上。

    还有什么关于这些记录的信息是你想告诉我的吗?

    每一条记录都是没有相关联数据的十六进制的正整数,并且没有一个数字能够同时出现超过一次的。

    

    这些谈话内容让这个问题变得清晰了。在美国,电话号码就是由一个三进制区号加上7位附加数字组成。一个真正由包括了许多信息的免费电话号码数据库。

    这个程序员当时在构建一个small corner of a system for processing 这样的一个数据库,并且这个电话数字是被排序了的。这个输入文件是这些数字的列表(删除了所有其他信息),并且他有一个错就是俩次包含了相同的号码。这个理想中的输出文件是一个包含了递增排序的数字。这个内容也定义了这个性能要求。在这个关于系统的会话中,这个使用者每小时强硬的要求一个被排序好的文件一次,并且不会做任何事在排序结束之前。这个排序因此不能够花费太多的时间(10秒最多了)


1.2 问题精确呈述

    为了程序员们,这些需求中总结起来就是:“我该怎么样去排序磁盘文件?”在我们攻克这个问题之前,让为我们用一个不那么偏执,更加有用的形式来整理一下已知信息。

    输入:    一个包含了许多n位正整数的文件,每一正整数都小于10^7。如果一些数字在输入中出现了俩次,那就是一个致命的错误。没有其他数据与这些数字有关联。

    输出:    一个将输入数字排出递增次序的列表。

    约束:    在主存缓冲区使用一些(粗略的)兆字节空间是允许的;可利用足够的磁盘空间。运行时间最多不超过几分钟。一次运行时间达到十秒就不必再减少了。

    感谢一分钟左右关于这个问题的说明。现在,你该怎样建议这个程序员呢?


1.3 程序设计

    这个程序使用了一个常规基于磁盘的归并算法作为一个出发点但是为了利用为门要为整数排序而削减了。用这几十行代码来替代俩百行,还让他运行速度更快。而复杂的代码或许还需要花费一些时间来理解并且调试运行成功。

    第二个解决方法更加符合这个排序问题的特殊性质。如果我们存储每一个号码用7个字节,那么我们能够存储大概143,000个号码在提供的兆字节中。如果我们把每一个号码表示为32位整数,那么我们就能够储存250,000个号码在这个兆字节中。我们也将因此使用一个能够做四十路并且转化为输入文件的程序。在第一路排序中,它从内存中读取了0到249,999个整数,排序这些最多250,000个整数并且把它们写在了输出文件中。第二路则排序了从250,000到499,999的数,并且依次往下一直有将要位9,750,000到9,999,999的第四十路排序。一个快速排序法将会十分有有效率对于排序主存缓冲区来说,并且这个只需要仅仅20行代码(我们将要在column 11中看见)。这整个程序将会因此被实施到这一俩篇幅的代码中。它也有一个令人满意的特性就是我们不再需要担忧使用临时磁盘文件;不幸的是,对这个优势,我们将付出了读取整个文件40次的代价。

    一个归并排序从输入中读取文件一次,在已经读取和写入多次的工作文件的辅助下去排序他,然后写入一次。

                                                                      WorkFiles

  /\   

                                                           many      |

             1   \/

Input File----》Merge Sort   -----》 Output File

    这个四十路排序算法读取了这个文件许多次并且只写入输出文件一次,未使用中间文件

                                         40

Input File----》Multipass Sort ----》Output FIle

    我们更希望用下面的这个设计结合了俩者的优点。读取一次,并且不使用临时中间文件。

  1   1

            Input File-----》Wonder Sort----》 Output  File

    我们可以做这个仅仅当我们想要在可利用的主存缓冲区中兆字节内取代所有的整数到输入文件。因此这个问题归结为我们是否可以标标最多一千万的不同整数在大概800万可用bit中。思考一下这个适当的表示。


1.4 实施

    从这个角度上看,这个位图或者位向量(representation of a set screams)供使用。我们可以表示这些小于20非负整数由20bits。比如,我们可以储存这个集和{1,2,3,5,8,13}用以下字符串:

         0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 

    这些比特代表,在集和中的为1,不在集和中为0。

    在这个问题中,每一个整数的这七个十进制数表示了少于一千万的一个号码。我们将表示这个文件通过这一千万个比特的字符串。仅仅当这个整数i在文件中,第几个比特是才存在。(这个程序员发现了俩百万个额外的比特;问题 5 调查当一个兆字节是限制时发生了什么)这个表示使用了三个这个问题的特点,而不是从排序问题中发现的:

    问题:输入时来自于相对小的范围,它不包涵重复,并且没有数据是联系了超越了整数的每一个记录。

    给出一个位图数据结构来表示在文件中的这一些数字,这个程序可以被写出三个阶段。第一段初始化了这个集和为空通过关掉所有比特。第二段为建立一个集和通过读入文件中每一个整数并且打开这个恰当的比特。第三段是生产这个排序过的输出文件通过检查每一个比特并且写出这个恰当的数字如果为1。若n是一个数字的bite在矢量中(10,000,000),这个程序可以被表示为如下psedocode(伪代码):应用的细节

/* phase 1: initalize set to empty */

for i = [0,n)

bit[i] = 0

/* phase 2 : insert present elements into the set */

for each i in the input file

bit[i] = 1

/* phase 3 : write sorted output */

for i = [0,n)

if bit[i] = 1

  write i on the output file

(再次声明,这个表示for i = [0,n)表示i从0到n-1

这个简述是非常浅显的对于这个程序员来解决它的问题来说。一些运用的细节他将面临到的已经被描述在问题2,5,7中了。


1.5 细则

这个程序员告诉了我关于电话的问题;大概花费了我们十五分钟来得到真正的问题并且发现了这个位图解决方案。它花费了他大

概俩个小时的时间去实施这个超越了那个一开始的几百行代码和一个星期的编程时间的程序的几十行的代码。并且这个程序十分的快

:当一个基于磁盘的归并算法或许会花费几分钟,这个程序仅仅需要大概10秒去读取与写入。解决方法 3 包括了对这个任务的配时细

则。

这些事实包括了第一节课中从这个案例我们要学习的:仔细的分析小问题有时可以产生巨大的实际效益。在这个案例中,几分钟的

仔细研究可以发现一个order of 大幅度的代码长度,程序员的时间与运行时间。General Chuck Yeager (第一位飞过声速的人)赞美

飞机的引擎道:简单粗暴。这个程序也展示出了这些特点。这个程序的特殊结构,然而,如果正确的格式被改变时,若想修改,非常

困难。除了这些优点以外,这个案例说明了下列的通用规则:

正确的问题。定义一个问题占解决问题的90%——我非常高兴这个程序员不会满足于为描述的第一个程序。问题10,11和12 有着

优美的解决方法 一旦 你提出正确的问题;在看提示和解决方法之前仔细的思考。

这个位图数据结构。这个数据结构表示了稠密的一个集和,立于有限范围当每一个元素出现最多一次并且没有数据是与元素相联系

。尽管这些条件不是令人满意的(当有复杂的元素或者额外的数据),一个来自于有限范围的关键可以被用于一个映射在有着复杂入

口的表。看问题6和8。

  多路算法。这些算法建立了几条路在输入数据上,每一次完成一点。我们在1.3可以看见40路算法;问题5鼓励了你去完善这个双路

算法。

时间与空间的权衡。程序的理论充满了时间与空间的权衡:使用更多的实际,一个程序运行时需要很少的空间。在解决方案5中的

个双路算法作为替换,也将花费双倍的运行时间去间半空间。让数据在主缓冲区而不是在磁盘中避免了一个进入磁盘的花销。

1.6 问题

可以从每一节书的背后找到提示和解决方案去选择问题。

1. 如果内存不限制,你该怎么去用一种语言实施一个排序在一个为了表示和排序集和的图书馆?

2. 你会怎么去实施一个使用按位运算逻辑操作的比特向量?

3. 运行时间效率是一个重要的部分对于设计目标来说,并且很影响这个结果程序。实施位图排序在你的系统上并且计算它的运行

间; 在问题一中,比较他和系统自带的排序。假设n 为 10,000,000,并且输入文件包含了1,000,000个整数。

4. 如果你认真的做了问题3,你会面临一个问题关于形成一个小于n的不重复的k整数。这个最简单的方法使用了第一个k的正整数

。这个极端的数据集没有修改这个位图方法的运行时间,但是它会影响这个系统排序的运行时间。你会怎么来形成一个有0-n-1的随

机序列的随机整数呢?为一个也由效率的的短程序努力!





ps:由于现在花费的时间与收获很不成比例,本类目暂停更新,待读完一遍后再做翻译。

个人英语水平较差,翻译此书是为了提高自己。若有翻译错点,恳请读者留言!谢谢!

原创粉丝点击