外部排序,则是每次进行部分排序,然后将各组部分排序的结果合并,再次排序得到最终的结果.

本文中的程序用最大堆和最大赢者树完成了一个外部排序算法,基本思想如下:

  1. 将N个元素的大数组拆成每个元素为M的子数组,得到X=(N/M)个子数组;
  2. 对这X个子数组,依次使用堆排序进行排序;
  3. 初始化最大赢者树:取出这X个子数组的最大元素,初始化一颗X个元素的最大赢者树;
  4. 每次从最大赢者树弹出一个最大值后,再从对应最大堆里面取出一个次大元素,补充进最大赢者树;
  5. 不停循环步骤4,直到所有最大堆里的元素都被弹出.

外存交互方式

为了便于分别管理子数组,这里使用文件存放每一个子数组,一个子数组存放到一个文件里面,记录下来其对应的文件名。使用vector fileNames记录每一个子数组文件名称。

子数组内部排序

这里使用c++ 提供的快速排序算法。从原始的输入文件里面一次读入vector arr,这里将arr大小,即每一个子数组大小,设置为heap_size。当数组存满时,便排序后写入外存文件,再清空数组。

  1. if(arr.size()==heap_size)
  2. {
  3. sort(arr.begin(),arr.end(),greater<int>());//降序排列
  4. out.open(fileNames[i++]);
  5. if (!out)
  6. {
  7. cout << "文件不能打开" <return -1;
  8. }
  9. for(int s:arr){
  10. out<" ";
  11. }
  12. out<close();
  13. arr.clear();
  14. }

最大赢者树

c++提供了现成的堆:优先队列,这里为了找到堆中每一个元素所属于哪一个子数组(文件),堆里存放的是元素和其对应的子数组的外存位置(文件名):pair = make_pair(待排序元素,所在文件名),采用自定义堆排序:根据待排序元素大小构建一个大顶堆

  1. /**
  2. 自构建比较函数
  3. **/
  4. struct cmp
  5. {
  6. bool operator()(Pair a, Pair b) {
  7. return a < b; //利用pair的重载运算符
  8. }
  9. };
  10. priority_queue, cmp > Tree; //使用大顶堆构造最大赢者树

初始化大顶堆后删除每一个子数组文件的头一个元素(最大值),表示该元素由外存引入内存。然后依次弹出大顶堆第一个元素(最大值)写入结果文件中。

每次从最大赢者树弹出一个最大值后,再从对应最大堆里面取出一个次大元素,补充进最大赢者树,刷新大顶堆。

这里都需要用到从文件中删除头一个元素的操作:

  1. /**
  2. * 删除文件头一个元素以及其后面的空格,并返回文件的第一个原素
  3. * 文件为空返回false*/
  4. bool delFirstElement(string fileName,int * firstElement)
  5. {
  6. vector<int> arr;
  7. int data;
  8. std::ifstream inputs(fileName);
  9. while(inputs>>data)
  10. {
  11. arr.push_back(data);
  12. }
  13. inputs.close();
  14. if(arr.empty())
  15. {
  16. return false;
  17. }
  18. std::ofstream outs(fileName);
  19. if(arr.size()>1)
  20. {
  21. for(int i = 1;isize();++i)
  22. outs<" ";
  23. }
  24. outs<0];
  25. outs.close();
  26. return true;
  27. }

测试案例生成

按道理,测试案例的输入文件应该是一个超过内存的足够多的数字的文件:比如存放了100亿个数字的文件。

这里为了测试方便,就从内存随机生成了一组数字,自己写到输入文件中

  1. bool generateData(string inputsFileName,int total_nums, int heap_size)
  2. {
  3. /*
  4. * 生成原始数据, 一行 heap_size 个元素,依此打印出来
  5. */
  6. ofstream out;
  7. out.open(inputsFileName);
  8. if (!out)
  9. {
  10. cout << "文件不能打开" <close();
  11. return false;
  12. }
  13. printf("Input Array: \n");
  14. for(int i = 1; i <= total_nums; i++)
  15. {
  16. int number = rand()%1000;;
  17. if(i%heap_size == 1)
  18. printf("%3d - %3d: ", i, i + heap_size-1);
  19. printf("%3d ", number);
  20. out<" ";
  21. if(i%heap_size == 0)
  22. {
  23. printf("\n");
  24. out<printf("\n\n");
  25. out<close();
  26. return true;
  27. }

完整代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <fstream>
  5. #include <iostream>
  6. #include <queue>
  7. #include <random>
  8. #include <time.h>
  9. #include <algorithm>
  10. #include <utility>
  11. #include <math.h>
  12. using namespace std;
  13. typedef pair<int,string> Pair;
  14. /**
  15. 自构建比较函数
  16. **/
  17. struct cmp
  18. {
  19. bool operator()(Pair a, Pair b) {
  20. return a < b; //利用pair的重载运算符
  21. }
  22. };
  23. bool generateData(string inputsFileName,int total_nums, int heap_size)
  24. {
  25. /*
  26. * 生成原始数据, 一行 heap_size 个元素,依此打印出来
  27. */
  28. ofstream out;
  29. out.open(inputsFileName);
  30. if (!out)
  31. {
  32. cout << "文件不能打开" <close();
  33. return false;
  34. }
  35. printf("Input Array: \n");
  36. for(int i = 1; i <= total_nums; i++)
  37. {
  38. int number = rand()%1000;;
  39. if(i%heap_size == 1)
  40. printf("%3d - %3d: ", i, i + heap_size-1);
  41. printf("%3d ", number);
  42. out<" ";
  43. if(i%heap_size == 0)
  44. {
  45. printf("\n");
  46. out<printf("\n\n");
  47. out<close();
  48. return true;
  49. }
  50. /**
  51. * 删除文件头一个元素以及其后面的空格,并返回文件的第一个原素
  52. * 文件为空返回false*/
  53. bool delFirstElement(string fileName,int * firstElement)
  54. {
  55. vector<int> arr;
  56. int data;
  57. std::ifstream inputs(fileName);
  58. while(inputs>>data)
  59. {
  60. arr.push_back(data);
  61. }
  62. inputs.close();
  63. if(arr.empty())
  64. {
  65. return false;
  66. }
  67. std::ofstream outs(fileName);
  68. if(arr.size()>1)
  69. {
  70. for(int i = 1;isize();++i)
  71. outs<" ";
  72. }
  73. outs<0];
  74. outs.close();
  75. return true;
  76. }
  77. int main(int argc, char **argv)
  78. {
  79. int total_nums, heap_size;
  80. int data;
  81. int = 0;
  82. int * firstElement = new int(0);
  83. vector<int> arr;
  84. ifstream inputs;
  85. ofstream out;
  86. string inputsFileName = "./data.txt";
  87. string outputFileName = "./sortedData.txt";
  88. vector fileNames;
  89. srand( (unsigned)time( NULL ) );//srand()函数产生一个以当前时间开始的随机种子
  90. if(argc != 3)
  91. {
  92. printf("Usage like : ./outerSort 10 100\n");
  93. return -1;
  94. }
  95. heap_size = atoi(argv[1]);
  96. total_nums = atoi(argv[2]);
  97. if(heap_size < 2) {
  98. printf("Usage: The 3th parameter should be larger than 1. Now it's %d.\n", heap_size);
  99. return -1;
  100. }
  101. if(total_nums <= heap_size) {
  102. printf("Usage: The 2nd parameter should be larger than the 3th parameter.\n");
  103. return -1;
  104. }
  105. = ceil((double)total_nums/heap_size);//子数组个数
  106. for(int i=0;i< X;++i)
  107. {
  108. fileNames.push_back("./out_"+to_string (i)+".txt");
  109. }
  110. if(!generateData(inputsFileName,total_nums, heap_size)){
  111. return -1;
  112. }
  113. inputs.open(inputsFileName);
  114. if (!inputs)
  115. {
  116. cout << "文件不能打开" <return -1;
  117. }
  118. int i = 0;
  119. //对这X个子数组,依次使用堆排序进行排序;
  120. while(inputs>>data)
  121. {
  122. arr.push_back(data);
  123. if(arr.size()==heap_size)
  124. {
  125. sort(arr.begin(),arr.end(),greater<int>());//降序排列
  126. out.open(fileNames[i++]);
  127. if (!out)
  128. {
  129. cout << "文件不能打开" <return -1;
  130. }
  131. for(int s:arr){
  132. out<" ";
  133. }
  134. out<close();
  135. arr.clear();
  136. }
  137. }
  138. inputs.close();
  139. //初始化最大赢者树:取出这X个子数组的最大元素,初始化一颗X个元素的最大赢者树;
  140. priority_queue, cmp > Tree; //使用大顶堆构造最大赢者树
  141. for(string name: fileNames)
  142. {
  143. int max;
  144. // cout<
  145. inputs.open(name);
  146. if (!inputs)
  147. {
  148. cout << "文件不能打开" <return -1;
  149. }
  150. inputs>>max; //第一个就是最大值
  151. Tree.push(make_pair(max,name));
  152. inputs.close();
  153. delFirstElement(name,firstElement);
  154. }
  155. //每次从最大赢者树弹出一个最大值后,再从对应最大堆里面取出一个次大元素,补充进最大赢者树;
  156. out.open(outputFileName);
  157. if (!out)
  158. {
  159. cout << "文件不能打开" <return -1;
  160. }
  161. while (!Tree.empty()) {
  162. cout << Tree.top().first << " ";
  163. out<< Tree.top().first << " " ;
  164. string fileName = Tree.top().second;
  165. Tree.pop();
  166. if(delFirstElement(fileName,firstElement))//如果不空,取出第一个元素放入最大赢者树
  167. {
  168. int secondmax = *firstElement;
  169. Tree.push(make_pair(secondmax,fileName));
  170. }
  171. }
  172. out.close();
  173. return 0;
  174. }

程序输出:./Sort 5 20,表示总共20个元素,拆成4个子数组,每个数组元素为5个.

C++ % ./outerSort 5 20
Input Array: 
  1 -   5: 763 375 478 948 822 
  6 -  10: 205 203 333 777  46 
 11 -  15: 175 883 926 696 413 
 16 -  20: 910 680 711 477 327 


948 926 910 883 822 777 763 711 696 680 478 477 413 375 333 327 205 203 175 46 %