“《编程珠玑》(第2版)第1章”:整数排序
作者在文中阐述了从该章实例引出来的一般原理:
正确的问题。明确问题,这场战役就成功了90%。
位图数据结构。该数据结构描述了一个有限定义域内的稠密集合,其中的每一个元素最多出现一次并且没有其他任何数据与该元素相关联。即使这些条件没有完全满足(例如,存在重复元素或额外的数据),也可以用有限定义域内的键作为一个表项更复杂的表格的索引。
多趟算法。这些算法多趟读入其输入数据,每次完成一步。
时间——空间折中与双赢。编程文献和理论中充斥着时间——空间的折中:通过使用更多的时间,可以减少程序所需的空间。但我的经验常常是这样的:减少程序的空间需求也会减少其运行时间。空间上高效的位图结构显著地减少了排序的运行时间。空间需求的减少之所以会导致运行时间的减少,有两个原因:需要处理的数据变少了,意味着处理这些数据的时间也变少了;同时将这些数据保存在内存中而不是磁盘上,进一步避免了磁盘访问的时间。当然了,只有在原始的设计远非最佳方案时,才有可能时空双赢。
简单的设计。Antoine de Saint-Exupery是法国作家兼飞机设计师,他曾经说过:“设计者确定其设计已经达到了完美的标准不是不能再增加任何东西,而是不能再减少任何东西。”更多的程序员应该使用该标准来检验自己完成的程序。简单的程序通常比具有相同功能的复杂的程序更可靠、更安全、更健壮、更高效,而且易于实现和维护。
Chuck Yeager将军(第一个超音速飞行的人)赞扬一架飞机的机械系统时用的词是“结构简单、部件很少、易于维护、非常坚固”。
在《编程珠玑》(第2版)的第1章(P4),作者对一个问题描述如下:
1. 问题描述
输入:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7。如果在输入文件中有任何整数重复出现就是致命错误。没有其他数据与该整数相关联。
输出:按升序排列的输入整数的列表。
约束:最多有(大约)1MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化了。
2. 问题分析
1MB大约有800万个位,如果需要排序的数据量小于或刚好等于1MB(单个数据的最大值不能超过1MB,而且任意两个数据不相等),那我们是否可以直接用这1MB个位来表示这个需要排序的数呢?问题当然是可以的。具体做法跟上一篇博文判断质数的几种方法的法五埃拉托斯特尼筛选法是一样的。这样做的结果,虽然占用空间大,但非常有时间优势。
3. 解决方法
作者在书中提出的做法是若给定文件中整数集合的位图数据结构,则可以分三阶段来编写程序:
1)将所有的位都置为0,从而将集合初始化为空;
2)通过读入文件中的每个整数来建立集合,将每个对应的位都置为1;
3)检验每一位,如果该位为1,就输出对应的整数,由此产生有序的输出文件。
4. 代码实现
本人实现的C++代码如下(如果已经知道输入的上限,就没有必要判断输入向量的最大值了,见下代码):
1 vector<int> SortNumbers::sort(vector<int> nums) 2 { 3 int sz = nums.size(); 4 int max = 0; 5 for (int i = 0; i < sz; i++) 6 { 7 if (nums[i] > max) 8 max = nums[i]; 9 } 10 11 vector<bool> isexist; 12 for (int i = 0; i < max + 1; i++) 13 isexist.push_back(false); 14 15 for (int i = 0; i < sz; i++) 16 isexist[nums[i]] = true; 17 18 vector<int> sorted; 19 for (int i = 0; i < max + 1; i++) 20 { 21 if (isexist[i]) 22 sorted.push_back(i); 23 } 24 25 return sorted; 26 }
注:bool类型在内存中是占一个字节的,而不是一位,这点得特别注意!
完整的代码及单元测试代码如下:
源代码:
1 #ifndef SORTNUMBERS_H 2 #define SORTNUMBERS_H 3 4 #include <iostream> 5 #include <vector> 6 using namespace std; 7 8 class SortNumbers 9 { 10 public: 11 SortNumbers(); 12 virtual ~SortNumbers(); 13 vector<int> sort(vector<int> nums); 14 15 }; 16 17 SortNumbers::SortNumbers() 18 { 19 20 } 21 22 SortNumbers::~SortNumbers() 23 { 24 25 } 26 27 vector<int> SortNumbers::sort(vector<int> nums) 28 { 29 int sz = nums.size(); 30 int max = 0; 31 for (int i = 0; i < sz; i++) 32 { 33 if (nums[i] > max) 34 max = nums[i]; 35 } 36 37 vector<bool> isexist; 38 for (int i = 0; i < max + 1; i++) 39 isexist.push_back(false); 40 41 for (int i = 0; i < sz; i++) 42 isexist[nums[i]] = true; 43 44 vector<int> sorted; 45 for (int i = 0; i < max + 1; i++) 46 { 47 if (isexist[i]) 48 sorted.push_back(i); 49 } 50 51 return sorted; 52 } 53 54 55 #endif
Boost单元测试代码(只证明其正确性):
1 #define BOOST_TEST_MODULE SortNumbers_Test_Module 2 3 #include "stdafx.h" 4 #include "../SortNumbers/SortNumbers.hpp" 5 6 struct SortNumbers_Fixture 7 { 8 SortNumbers_Fixture() 9 { 10 test = new SortNumbers; 11 } 12 ~SortNumbers_Fixture() 13 { 14 delete test; 15 } 16 17 SortNumbers * test; 18 }; 19 20 BOOST_FIXTURE_TEST_SUITE(SortNumbers_Test_Suite, SortNumbers_Fixture) 21 22 BOOST_AUTO_TEST_CASE( Sort ) 23 { 24 int in_array[15] = { 22, 11, 17, 4, 5, 33, 13, 1, 20, 2, 30, 300, 222, 55, 43 }; 25 int out_array[15] = { 1, 2, 3, 4, 5, 11, 13, 17, 20, 22, 30, 43, 55, 222, 300 }; 26 vector<int> in, out; 27 for (int i = 0; i < 15; i++) 28 in.push_back(in_array[i]); 29 30 out = test->sort(in); 31 for (int i = 0; i < 15; i++) 32 BOOST_REQUIRE(out[i] = out_array[i]); 33 34 } 35 36 37 BOOST_AUTO_TEST_SUITE_END()