排序是编程中很常用的操作。当要排序的数据量很大,无法一次装入内存时,就要使用外部排序。
外部排序通常的做法是先把数据分成多个可以一次装入内存的小段,对这些段分别使用内部排序,将排好序的段依次写入磁盘,再进行多路归并。
多路归并通常用败者树来加速,但既然stl里有现成的priority_queue,我们可以偷个懒,不去重复发明轮子。
废话少说,我们直接上程序。
首先是数据生成器,不做其他什么操作,只是像data.dat中写入一些随机数,注意我们这里生成的全是正整数,运行这个程序一段时间以后我们按下ctrl+C。
1 #include <fstream> 2 #include <ctime> 3 #include <cstdlib> 4 using namespace std; 5 6 int main() 7 { 8 ofstream os("data.dat"); 9 srand(time(NULL)); 10 while(true) 11 os << rand() << endl; 12 }
接下来是我们的主要程序:
1 #include <iostream> 2 #include <fstream> 3 #include <sstream> 4 #include <cmath> 5 #include <algorithm> 6 #include <cstdio> 7 #include <map> 8 #include <vector> 9 #include <queue> 10 11 class ExternalSort 12 { 13 //分段排序并写入文件 14 struct FilePart 15 { 16 int index; 17 std::string file; 18 std::ifstream in; 19 int next; 20 FilePart(int i) : index(i), file(""), in(), next(-2) 21 { 22 std::ostringstream os; 23 os << ".temp." << index << ".dat"; 24 file = os.str(); 25 } 26 void writeFile(int* array, int sz) 27 { 28 std::cout << file << " created" << std::endl; 29 std::ofstream of(file.c_str()); 30 for(int i = 0; i < sz; i++) 31 of << array[i] << std::endl; 32 in.open(file.c_str()); 33 } 34 35 int peek() 36 { 37 if(next == -2) 38 getNext(); 39 return next; 40 } 41 42 int getNext() 43 { 44 in >> next; 45 if(!in) 46 { 47 next = -1; 48 return next; 49 } 50 return next; 51 } 52 ~FilePart() 53 { 54 if(remove(file.c_str())== 0)//删除临时文件 55 std::cout << file << " deleted" << std::endl; 56 } 57 }; 58 59 //比较器,注意这里用的是大于,这样使得优先队列每次top都访问到拥有最小的next的FilePart 60 struct Comparator 61 { 62 bool operator()(FilePart* fp1, FilePart* fp2) 63 { 64 return fp1->peek() > fp2->peek(); 65 } 66 }; 67 const static int READ_NUM = 10000000;//每次读取一千万个数 68 std::ifstream input; 69 int k; 70 std::vector<FilePart*> forDelete; 71 public: 72 ExternalSort(const char* fn) : input(fn), forDelete() {} 73 74 void sort() 75 { 76 split(); 77 merge(); 78 } 79 //快速排序 80 void quickSort(int* array, int a, int b) 81 { 82 if(a < b) 83 { 84 int mid = partition(array, a, b); 85 quickSort(array, a, mid - 1); 86 quickSort(array, mid + 1, b); 87 } 88 } 89 90 ~ExternalSort() 91 { 92 for(std::vector<FilePart*>::iterator it = forDelete.begin(); it != forDelete.end(); ++it) 93 if(*it) 94 delete *it; 95 } 96 97 private: 98 int partition(int* array, int a, int b) 99 { 100 int key = array[b]; 101 int index = a; 102 for(int i = a; i < b; i++) 103 if(array[i] < key) 104 std::swap(array[i], array[index++]); 105 std::swap(array[b], array[index]); 106 return index; 107 } 108 109 void merge() 110 { 111 std::priority_queue<FilePart*, std::vector<FilePart*>, Comparator> pq(forDelete.begin(), forDelete.end()); 112 std::ofstream os("output.dat"); 113 while(pq.size()) 114 { 115 FilePart* fp = pq.top();//取得拥有最小的next的FilePart 116 pq.pop();//从队列中删除它 117 int num = fp->peek(); 118 os << num << std::endl;//写入输出文件 119 if(fp->getNext() != -1)//取得下一个值,由于我们的数据都是正整数,得到-1意味着已到达文件末尾 120 pq.push(fp);//如果有下一个,就将它再次加入队列 121 } 122 } 123 124 void split() 125 { 126 k = 0; 127 while(input) 128 { 129 int* buf = new int[READ_NUM]; 130 int i = 0; 131 for(; i < READ_NUM && input; i++) 132 input >> buf[i]; 133 if(!input) 134 i--; 135 if(i == 0) 136 continue; 137 quickSort(buf, 0, i - 1);//排序 138 FilePart* fp = new FilePart(k); 139 forDelete.push_back(fp); 140 fp->writeFile(buf, i);//写入临时文件 141 k++; 142 delete[] buf; 143 } 144 } 145 };
main函数:
int main(int argc, char** argv) { if(argc != 2) { cout << "Usage sort <file>" << endl; exit(1); } ExternalSort es(argv[1]); clock_t c1 = clock(); es.sort(); clock_t c2 = clock(); float t = (c2 - c1) / CLOCKS_PER_SEC; cout << "Sort done in " << t << " seconds." << endl; }
测试电脑比较烂,2.6G的测试数据花了了1378秒才跑完