下面的 C 程序使用 C 标准库函数 qsort 来排序一个整数文件。
int intcomp(int *x, int *y)
return *x - *y;
int a[1000000];
int main(void) {
int i, n = 0;
while (scanf("%d", &a[n]) != EOF)
n++;
qsort(a, n, sizeof(int), intcomp);
for (i = 0; i < n; i++)
printf("%d\n", a[i]);
return 0;
}
下面这个 C++ 程序使用 C++ 标准模板库中的 set 容器完成相同的任务。
int main(void) {
set<int> S;
int i;
set<int>::iterator j;
while (cin >> j)
S.insert(i);
for (j = S.begin(); j != S.end(); ++j)
cout << *j << "\n";
return 0;
}
答案 3 概述了上面两个程序的性能。
下面的函数使用常量来设置、清除以及测试位值:
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N / BITSPERWORD];
void set(int i) {
a[i >> SHIFT] |= (1 << (i & MASK));
}
void clr(int i) {
a[i >> SHIFT] &= ~(1 << (i & MASK));
}
int test(int i) {
return a[i >> SHIFT] & (1 << (i & MASK));
}
下面的 C 代码使用答案 2 中定义的函数来实现排序算法。
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;
}
我使用答案 4 中的程序生成了一个包含 100 万个不同正整数的文件,其中每个正整数都小于 1 000 万。下表列出了使用系统命令行排序、答案 1 中的 C++ 和 C 程序以及位图代码对这些整数进行排序的开销:
系统排序 | C++/STL | C/qsort | C/位图 | |
---|---|---|---|---|
总时间开销(秒) | 89 | 38 | 12.6 | 10.7 |
计算时间开销(秒) | 79 | 28 | 2.4 | 0.5 |
空间开销(MB) | 0.8 | 70 | 4 | 1.25 |
第一行是总时间,第二行减去了读写文件所需的 10.2 秒输入/输出时间。尽管通用 C++ 程序所需的内存和 CPU 时间是专用 C 程序的 50 倍,但是代码量仅有专用 C 程序的一半,而且扩展到其他问题也容易得多。
见第 12 章,尤其是习题 12.8。下面的代码假定 randint(l, u)返回 l…u 中的一个随机整数。
for i = [0, n)
x[i] = i
for i = [0, k)
swap(i, randint(i, n - 1))
print x[i]
其中swap函数的作用是交换 x 中的两个元素。有关 randint 函数的详细讨论见 12.1 节。
使用位图表示 1 000 万个数需要 1000 万个位,或者说 125 万字节。考虑到没有以数字 0 或 1 打头的电话号码,我们可以将内存需求降低为 100 万字节。另一种做法是采用两趟算法,首先使用 5000 000 / 8 = 625 000 个字的存储空间来排序 0 ~ 4 999 999 之间的整数,然后在第二趟排序 5 000 000 ~ 9 999 999 的整数。k 趟算法可以在 kn 的时间开销和 n / k 的空间开销内完成对最多 n 个小于 n 的无重复正整数的排序。
如果每个整数最多出现 10 次,那么我们就可以使用 4 位的半字节来统计它出现的次数。利用习题 5 的答案,我们可以使用 10 000 000 / 2 个字节在 1 趟内完成对整个文件的排序,或使用 10 000 000 / 2k 个字节在 k 趟内完成对整个文件的排序。
借助于两个额外的 n 元向量 from、to 和一个整数 top,我们就可以使用标识来初始化向量 data[0…n-1]。如果元素 data[i]已初始化,那么 from[i] < top 并且 to[from[i]] = i。因此,from 是一个简单的标识,to 和 top 一起确保了 from 中不会被写入内存里的随机内容。变量 top 初始为 0,下面的代码实现对数组元素 i 的首次访问:
from[i] = top
to[top] = i
data[i] = 0
top++
商店将纸质订单表格放在 10 * 10 的箱数组中,使用客户电话号码的最后两位作为散列索引。当客户打电话下订单时,将订单放到适当的箱中。当客户来取商品时,销售人员顺序搜索对应箱中的订单——这就是经典的“用顺序搜索来解决冲突的开放散列”。电话号码的最后两位数字非常接近于随机,因此是非常理想的散列函数,而最前面的两位数字则很不理想——为什么?一些市政机关使用类似的方案在记事本中记录信息。
两地的计算机原先是通过微波连接的,但是当时测试站打印图纸所需的打印机却非常昂贵。因此,该团队在主厂绘制图纸,然后拍摄下来并通过信鸽把 35 毫米的底片送到测试站,在测试站进行放大并打印成图片。鸽子来回一次需要 45 分钟,是汽车所需时间的一半,并且每天只需要花费几美元。在项目开发的 16 个月中,信鸽传送了几百卷底片,仅丢失了两卷(当地有鹰,因此没有让信鸽传送机密数据)。由于现在打印机比较便宜,因此可以使用微波链路解决该问题。
根据该传闻,前苏联人用铅笔解决了这个问题。有关这个真实故事的背景请查看www.spacepen.com(刚看了下,价格还可以接受)。Fisher Space Pen 公司成立于 1948 年,其书写设备被俄国航天局、水底探测人员和喜马拉雅登山探险队使用过。