《编程珠玑》第一章

案例:一个最多包含n个正整数的磁盘文件,每个数都小于n,其中n=10^7,文件中不包含重复的数。要求输出按升序排列的输入整数的列表。 Note:最多有(大约)1MB的内存可用,有充足的磁盘空间可用。

思路与解决方案:

第一:

首先,显而易见的方法是以一般的基于磁盘的归并排序(不了解的可以请教度娘)为起点,当然可能由于一些特殊情况,对大体的程序做一个微调,但是这也丝毫不会影响这个程序可能还要运行几天的事实。

第二:

考虑到该问题的特殊性,每个数都小于10^7,且不能重复。很显然能用一个包含10 000 000个位的向量来表示所有小于10^7的正整数,出现过的数即把相应的位置1,没出现过的数即置0,这样就大大降低了程序运行时间。这种方法简而言之就是:用位图或位向量表示集合

继续探讨,1MB最多能表示1024*1024*8约为8 000 000个位,但是这里最多包含10 000 000个元素(即使用10 000 000个位),怎无奈表示不下。这里,为了说明位向量怎么使用以及程序怎么编写,我们假设有2MB内存,在后面我们再去研究表示不下的情况。

接下来,我们来实现该程序。假设已经声明表示文件中整数集合的位向量,可以分三个步骤来编写程序。第一阶段将所有的位置0,从而将集合初始化为空;第二阶段通过读入文件中的每个整数来建立集合,将每个对应的位都置为1;第三阶段检验每一位,如果该位为1,就输出对应的整数,由此产生有序的输出文件。

伪代码如下:

/* phase 1: initialize 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

编写的C代码如下:

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000

int a[1 + N / BITSPERWORD]; //位向量

void set(int i) {   //设置出现过的数,置1
    a[i >> SHIFT] |= (1 << (i & MASK));
}

void clr(int i) {   //清除,将每一位置0
    a[i >> SHIFT] &= ~(1 << (i & MASK));
}

int test(int i) {   //检验某一位是否为1
    return a[i >> SHIFT] & (i << (i & MASK));
}

int main(void) {
    int i;
    for (i = 0; i < N; i++) {
        clr(i);
    }
    while (scanf("%d", &i) != EOF) {
        set(i);
    }
    for (i = 0; i < N; i++) {
        if (test(i)) {
            printf("%d\n", i);
        }
    }
    return 0;
}

如果你对位运算不怎么感冒(个人建议认真学习,文末我会稍微提及一下位运算),花多点时间也要吃透该code,因为实在是太精辟了,寥寥几行解决了几十年前令程序员棘手的问题。

然后,探讨用1MB内存表示不了10 000 000个位的情况。我没有给出该题的背景,如果考虑到背景的话,实际上可以将10 000 000个位降低为8 000 000个位,那么1MB可以说刚刚好;其次,我们可以采用两趟算法,首先使用5 000 000/8=625 000个字节的存储空间来排序0~4 999 999之间的整数,然后第二趟排序5 000 000~9 999 999的整数。k趟算法可以在kn的时间开销和n/k的空间开销内完成对最多n个小于n的无重复正整数的排序。

Summary:

位图数据结构。该数据结构描述了一个有限定义域内的稠密集合,其中的每一个元素最多出现一次并且没有任何数据结构与该元素相关联。即使这些条件没有完全满足(例如,存在重复元素或额外的数据),也可以用有限定义域内的键作为一个表项或者更复杂的表格的索引。正确理解该数据结构以及学会如何使用、在什么场合使用,如此方能得心应手。

ps:
1.将一个整数n除以2,我以前会写成 n = n / 2,但现在我会用 n = n >> 1;位运算其实是速度很快的一种运算方式,如果写成第一种方式(没有一点毛病,甚至不能太对),但是编译器会将它优化成第二种形式,所以,不用多讲,你应该能明白道理了。
2.判断一个数n是不是偶数?相信你以后不会再用 if n%2 == 0,而是会采用 if n&1 == 0。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容