海量数据处理之外排序

前言:

本文是对July博文http://blog.youkuaiyun.com/v_JULY_v/article/details/6451990的一些总结

现在先让我们来看一道有关外排序的题:

问题描述:
输入:一个最多含有n个不重复的正整数(也就是说可能含有少于n个不重复正整数)的文件,其中每个数都小于等于n,且n=10^7。
输出:得到按从小到大升序排列的包含所有输入的整数的列表。
条件:最多有大约1MB的内存空间可用,但磁盘空间足够。且要求运行时间在5分钟以下,10秒为最佳结果。

本题有很多种解法,列出一下几种:

分析:下面咱们来一步一步的解决这个问题,
    1、归并排序。你可能会想到把磁盘文件进行归并排序,但题目要求你只有1MB的内存空间可用,所以,归并排序这个方法不行。
    2、位图方案。熟悉位图的朋友可能会想到用位图来表示这个文件集合。例如正如编程珠玑一书上所述,用一个20位长的字符串来表示一个所有元素都小于20的简单的非负整数集合,边框用如下字符串来表示集合{1,2,3,5,8,13}:

0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0

上述集合中各数对应的位置则置1,没有对应的数的位置则置0。

    参考编程珠玑一书上的位图方案,针对我们的10^7个数据量的磁盘文件排序问题,我们可以这么考虑,由于每个7位十进制整数表示一个小于1000万的整数。我们可以使用一个具有1000万个位的字符串来表示这个文件,其中,当且仅当整数i在文件中存在时,第i位为1。采取这个位图的方案是因为我们面对的这个问题的特殊性:1、输入数据限制在相对较小的范围内,2、数据没有重复,3、其中的每条记录都是单一的整数,没有任何其它与之关联的数据。
    所以,此问题用位图的方案分为以下三步进行解决:

  • 第一步,将所有的位都置为0,从而将集合初始化为空。
  • 第二步,通过读入文件中的每个整数来建立集合,将每个对应的位都置为1。
  • 第三步,检验每一位,如果该位为1,就输出对应的整数。

    经过以上三步后,产生有序的输出文件。令n为位图向量中的位数(本例中为1000 0000),程序可以用伪代码表示如下:

  1. //磁盘文件排序位图方案的伪代码 
  2. //copyright@ Jon Bentley 
  3. //July、updated,2011.05.29。 
  4.  
  5. //第一步,将所有的位都初始化为0 
  6. for i ={0,....n}     
  7.    bit[i]=0; 
  8. //第二步,通过读入文件中的每个整数来建立集合,将每个对应的位都置为1。 
  9. for each i in the input file    
  10.    bit[i]=1; 
  11.  
  12. //第三步,检验每一位,如果该位为1,就输出对应的整数。 
  13. for i={0...n}     
  14.   if bit[i]==1       
  15.     write i on the output file 
  1. //磁盘文件排序位图方案的伪代码  
  2. //copyright@ Jon Bentley  
  3. //July、updated,2011.05.29。  
  4.   
  5. //第一步,将所有的位都初始化为0  
  6. for i ={0,....n}      
  7.    bit[i]=0;  
  8. //第二步,通过读入文件中的每个整数来建立集合,将每个对应的位都置为1。  
  9. for each i in the input file     
  10.    bit[i]=1;  
  11.   
  12. //第三步,检验每一位,如果该位为1,就输出对应的整数。  
  13. for i={0...n}      
  14.   if bit[i]==1        
  15.     write i on the output file  

    上面只是为了简单介绍下位图算法的伪代码之抽象级描述。显然,咱们面对的问题,可不是这么简单。下面,我们试着针对这个要分两趟给磁盘文件排序的具体问题编写完整代码,如下。

3、多路归并。

1、内存排序
由于要求的可用内存为1MB,那么每次可以在内存中对250K的数据进行排序,然后将有序的数写入硬盘。
那么10M的数据需要循环40次,最终产生40个有序的文件。
2、归并排序

  1. 将每个文件最开始的数读入(由于有序,所以为该文件最小数),存放在一个大小为40的first_data数组中;
  2. 选择first_data数组中最小的数min_data,及其对应的文件索引index;
  3. 将first_data数组中最小的数写入文件result,然后更新数组first_data(根据index读取该文件下一个数代替min_data);
  4. 判断是否所有数据都读取完毕,否则返回2。

所以,本程序按顺序分两步,第一步、Memory Sort,第二步、Merge Sort。程序的流程图,如下图所示(感谢F的绘制)。

然后,编写的完整代码如下:

首先我们将生成大数据量(1000万)的程序如下:

 

  1. 01.//purpose:  生成随机的不重复的测试数据    
  2. 02.//copyright@ 2011.04.19 yansha    
  3. 03.//1000w数据量,要保证生成不重复的数据量,一般的程序没有做到。    
  4. 04.//但,本程序做到了。    
  5. 05.//July、2010.05.30。    
  6. 06.#include <iostream>    
  7. 07.#include <time.h>    
  8. 08.#include <assert.h>    
  9. 09.using namespace std;    
  10. 10.    
  11. 11.const int size = 10000000;    
  12. 12.int num[size];    
  13. 13.    
  14. 14.int main()    
  15. 15.{    
  16. 16.    int n;    
  17. 17.    FILE *fp = fopen("data.txt", "w");    
  18. 18.    assert(fp);    
  19. 19.    
  20. 20.    for (n = 1; n <= size; n++)      
  21. 21.        //之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。    
  22. 22.        num[n] = n;    
  23. 23.    srand((unsigned)time(NULL));    
  24. 24.    int i, j;    
  25. 25.    
  26. 26.    for (n = 0; n < size; n++)    
  27. 27.    {    
  28. 28.        i = (rand() * RAND_MAX + rand()) % 10000000;    
  29. 29.        j = (rand() * RAND_MAX + rand()) % 10000000;    
  30. 30.        swap(num[i], num[j]);    
  31. 31.    }    
  32. 32.    
  33. 33.    for (n = 0; n < size; n++)    
  34. 34.        fprintf(fp, "%d ", num[n]);    
  35. 35.    fclose(fp);    
  36. 36.    return 0;    
  37. 37.}    
  1. 01.//copyright@ yansha    
  2. 02.//July、updated,2011.05.28。    
  3. 03.#include <iostream>    
  4. 04.#include <string>    
  5. 05.#include <algorithm>    
  6. 06.#include <time.h>    
  7. 07.using namespace std;    
  8. 08.    
  9. 09.int sort_num = 10000000;    
  10. 10.int memory_size = 250000;      
  11. 11.    
  12. 12.//每次只对250k个小数据量进行排序    
  13. 13.int read_data(FILE *fp, int *space)    
  14. 14.{    
  15. 15.    int index = 0;    
  16. 16.    while (index < memory_size && fscanf(fp, "%d ", &space[index]) != EOF)    
  17. 17.        index++;    
  18. 18.    return index;    
  19. 19.}    
  20. 20.    
  21. 21.void write_data(FILE *fp, int *space, int num)    
  22. 22.{    
  23. 23.    int index = 0;    
  24. 24.    while (index < num)    
  25. 25.    {    
  26. 26.        fprintf(fp, "%d ", space[index]);    
  27. 27.        index++;    
  28. 28.    }    
  29. 29.}    
  30. 30.    
  31. 31.// check the file pointer whether valid or not.    
  32. 32.void check_fp(FILE *fp)    
  33. 33.{    
  34. 34.    if (fp == NULL)    
  35. 35.    {    
  36. 36.        cout << "The file pointer is invalid!" << endl;    
  37. 37.        exit(1);    
  38. 38.    }    
  39. 39.}    
  40. 40.    
  41. 41.int compare(const void *first_num, const void *second_num)    
  42. 42.{    
  43. 43.    return *(int *)first_num - *(int *)second_num;    
  44. 44.}    
  45. 45.    
  46. 46.string new_file_name(int n)    
  47. 47.{    
  48. 48.    char file_name[20];    
  49. 49.    sprintf(file_name, "data%d.txt", n);    
  50. 50.    return file_name;    
  51. 51.}    
  52. 52.    
  53. 53.int memory_sort()    
  54. 54.{    
  55. 55.    // open the target file.    
  56. 56.    FILE *fp_in_file = fopen("data.txt", "r");    
  57. 57.    check_fp(fp_in_file);    
  58. 58.    int counter = 0;    
  59. 59.    while (true)    
  60. 60.    {    
  61. 61.        // allocate space to store data read from file.    
  62. 62.        int *space = new int[memory_size];    
  63. 63.        int num = read_data(fp_in_file, space);    
  64. 64.        // the memory sort have finished if not numbers any more.    
  65. 65.        if (num == 0)    
  66. 66.            break;    
  67. 67.    
  68. 68.        // quick sort.    
  69. 69.        qsort(space, num, sizeof(int), compare);    
  70. 70.        // create a new auxiliary file name.    
  71. 71.        string file_name = new_file_name(++counter);    
  72. 72.        FILE *fp_aux_file = fopen(file_name.c_str(), "w");    
  73. 73.        check_fp(fp_aux_file);    
  74. 74.    
  75. 75.        // write the orderly numbers into auxiliary file.    
  76. 76.        write_data(fp_aux_file, space, num);    
  77. 77.        fclose(fp_aux_file);    
  78. 78.        delete []space;    
  79. 79.    }    
  80. 80.    fclose(fp_in_file);    
  81. 81.    
  82. 82.    // return the number of auxiliary files.    
  83. 83.    return counter;    
  84. 84.}    
  85. 85.    
  86. 86.void merge_sort(int file_num)    
  87. 87.{    
  88. 88.    if (file_num <= 0)    
  89. 89.        return;    
  90. 90.    // create a new file to store result.    
  91. 91.    FILE *fp_out_file = fopen("result.txt", "w");    
  92. 92.    check_fp(fp_out_file);    
  93. 93.    
  94. 94.    // allocate a array to store the file pointer.    
  95. 95.    FILE **fp_array = new FILE *[file_num];    
  96. 96.    int i;    
  97. 97.    for (i = 0; i < file_num; i++)    
  98. 98.    {    
  99. 99.        string file_name = new_file_name(i + 1);    
  100. 100.        fp_array[i] = fopen(file_name.c_str(), "r");    
  101. 101.        check_fp(fp_array[i]);    
  102. 102.    }    
  103. 103.    
  104. 104.    int *first_data = new int[file_num];       
  105. 105.    //new出个大小为0.1亿/250k数组,由指针first_data指示数组首地址    
  106. 106.    bool *finish = new bool[file_num];    
  107. 107.    memset(finish, false, sizeof(bool) * file_num);    
  108. 108.    
  109. 109.    // read the first number of every auxiliary file.    
  110. 110.    for (i = 0; i < file_num; i++)    
  111. 111.        fscanf(fp_array[i], "%d ", &first_data[i]);    
  112. 112.    while (true)    
  113. 113.    {    
  114. 114.        int index = 0;    
  115. 115.        while (index < file_num && finish[index])    
  116. 116.            index++;    
  117. 117.    
  118. 118.        // the finish condition of the merge sort.    
  119. 119.        if (index >= file_num)    
  120. 120.            break;    
  121. 121.        //主要的修改在上面两行代码,就是merge sort结束条件。    
  122. 122.        //要保证所有文件都读完,必须使得finish[0]...finish[40]都为真    
  123. 123.        //July、yansha,555,2011.05.29。    
  124. 124.    
  125. 125.        int min_data = first_data[index];    
  126. 126.        // choose the relative minimum in the array of first_data.    
  127. 127.        for (i = index + 1; i < file_num; i++)    
  128. 128.        {    
  129. 129.            if (min_data > first_data[i] && !finish[i])       
  130. 130.                //一旦发现比min_data更小的数据first_data[i]    
  131. 131.            {    
  132. 132.                min_data = first_data[i];        
  133. 133.                //则置min_data<-first_data[i]index = i;                       
  134. 134.                //把下标i 赋给index。    
  135. 135.            }    
  136. 136.        }    
  137. 137.    
  138. 138.        // write the orderly result to file.    
  139. 139.        fprintf(fp_out_file, "%d ", min_data);    
  140. 140.        if (fscanf(fp_array[index], "%d ", &first_data[index]) == EOF)    
  141. 141.            finish[index] = true;    
  142. 142.    }    
  143. 143.    
  144. 144.    fclose(fp_out_file);    
  145. 145.    delete []finish;    
  146. 146.    delete []first_data;    
  147. 147.    for (i = 0; i < file_num; i++)    
  148. 148.        fclose(fp_array[i]);    
  149. 149.    delete [] fp_array;    
  150. 150.}    
  151. 151.    
  152. 152.int main()    
  153. 153.{    
  154. 154.    clock_t start_memory_sort = clock();    
  155. 155.    int aux_file_num = memory_sort();    
  156. 156.    clock_t end_memory_sort = clock();    
  157. 157.    cout << "The time needs in memory sort: " << end_memory_sort - start_memory_sort << endl;    
  158. 158.    clock_t start_merge_sort = clock();    
  159. 159.    merge_sort(aux_file_num);    
  160. 160.    clock_t end_merge_sort = clock();    
  161. 161.    cout << "The time needs in merge sort: " << end_merge_sort - start_merge_sort << endl;    
  162. 162.    system("pause");    
  163. 163.    return 0;    
  164. 164.}    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值