编程珠玑-第一章 位图排序

主要有以下收获:

1,如果有一个文件里面有n个数,需要对这n个数进行排序,但是内存很小,怎么办?

通用的归并排序:假设内存每次只能排m个数,则首先分l=n/m+1次分别把n个数读入内存排序,并且存入l个文件中,然后对这l个文件里面的数进行归并排序。 如果m>=l, 这是最好的情况,直接每次读入每个文件最小的数,选出最小的写到输出文件中,直到所有文件的所有数都写完为止;如果m<l,那么还需要递归归并排序,l个文件继续分批排序写到中间文件中,直到文件数量小于m,就可以进行最后一趟归并,结果写入输出文件

时间复杂度:这个算起来有点麻烦,第一部分是l*O(logm),第二部分是一个递归,用递归树能够求出时间是

lm*m*log(m,l),大概是nm*log(m,n)

空间复杂度:需要茫茫多的中间文件

2,位图排序

实现的时候有一个小技巧,我们经常用到这样的算式:n/m和n%m的结果,在m是2的幂的时候有一个很好的方法是:n%m = n&(m-1),而n/m = n>>log(m)这个倒是比较常见的

3,怎样产生0~n-1中k个不重复的随机数?

这个问题终于在这儿找到了很好的方法,为了以后能记住这个方法,或者能够利用这个方法按照自己的思维扩展一些其他的问题,把这道题我的思维过程写下来:

给定一个产生n以内随机数的函数非常容易,难处是怎样保证不重复呢?最简单的方法是,产生过的数,去除掉不就可以了嘛,关键是怎么去除才能使得复杂度最小,直接去掉的复杂度是O(n),这样下一次产生n-1以内的随机数就可以了;如果原来的数是用链表存储的,删除的复杂度是O(1),但是根据随机的索引得到相应的数的复杂度则是O(n),还是跟上面的一样

 

这样就好了:rand(s,t) returns a random number in s~t

for i = 1~k

swap(a[i],a[rand(i,n)]

把产生过的数字换到数组的前面,这样就可以很方便的排除已经产生过的数

4,这个问题是习题9中的问题,位图排序中肯定要首先把用来记录的位初始化,但是怎样用额外的空间去避免初始化而节约时间呢?

这个解答我还真没想出来,看了后面的解答,虽然够简单,但是还是很绕人,来分析分析想出这个答案的过程哈:

数组A是准备做记录的数组,A没有初始化,假设有了A[2]记录为1,因为A其它的数都是随机的,可能A中有很多数值是1的,我怎么知道A[2]是有记录的呢?

另外开一个数组B,设置B[2]=true,行吗?不行唉,因为这样B就需要全部初始化为false,还不如直接初始化A呢,那怎么记录呢?从B[0]开始记录改变过的数据的序号,top记录当前索引,因此,B[0]记录为2,top=1,那么最终有效地数据就是索引为B[0]到B[top-1]的A中的数据

 

从功能上讲,这样是解决问题了,如果需要找出所有A中被记录了的元素,只要遍历一遍B中top以内的数就可以了。但是如果需要知道A[i]有没有被记录过,怎么办呢?需要搜索一遍B[0~top-1],每次都需要这样,太不方便了,还不如初始化呢,问题的根源是我们只知道怎么根据B中的元素找到A中对应的元素,但是不晓得怎么从A[i]直接找到B中对应的数据,如果每一个A[i]都有一个指针,指向B中对应的元素,那么这个问题就解决了,所以再次用空间换时间,为每个A[i]分配一个指针,指向对应的B中的位置。

这样问题就彻底解决了,确定A[i]是不是被记录过的方法是,根据A[i]的指针指向的B中的索引,看是不是记录了i的

答案中给出的指针的方法是,开一个数组C,C[i]用来记录A[i]在B中的索引

 

多数精妙的方法往往也不是一下子就能想出来的,而是从本来很一般的方法,一步步根据需求,改进得到的。所以平时做事儿的时候,不用因为方法挫而不去做,或者觉得这个方法不够满意而犹豫不决,如果真的想不到更好的方法,可以先按照挫的方法做,等到自己被自己方法中的各种不方便苦恼到的时候,自己会去认真思考怎么改进的,这个是最近的一些体会。

posted on 2011-08-09 18:54  pheonix_nju  阅读(292)  评论(3)    收藏  举报

导航