首页 > 代码库 > 如何给10^7个数据量的磁盘文件进行排序--归并排序

如何给10^7个数据量的磁盘文件进行排序--归并排序

接上面的题目,假若待排序的数据有重复的呢?这里采用的是归并排序。


1、算法分析: 
    1、稳定性:归并排序是一种稳定的排序。
    2、存储结构要求:可用顺序存储结构。也易于在链表上实现。
    3、时间复杂度: 对长度为n的文件,需进行lgn趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlgn)。。
    4、空间复杂度:需要一个辅助向量来暂存两有序子文件归并的结果,故其辅助空间复杂度为O(n),显然它不是就地排序。
 
2、总结
      与快速排序相比,归并排序的最大特点是,它是一种稳定的排序方法。归并排序一般多用于外排序。但它在内排方面也占有重要地位,因为它是基于比较的时间复杂度为O(N*Log(N))的排序算法中唯一稳定的排序,所以在需要稳定内排序时通常会选择归并排序。归并排序不要求对序列可以很快地进行随机访问,所以在链表排序的实现中很受欢迎。
 
3、编程实现

ok,接下来,我们来编程实现上述磁盘文件排序的问题,本程序由两部分构成:
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。
     
代码分析:
1.第一步显然是将数据分到file_num文档中,每个文档有250000个数据,并且每个文档数据我们都采用快速排序qsort进行排序。
伪代码:
  1. while(true)
  2. {
  3. num=ReadFile( fp_unSortFile, each_file);//从未排序的数据中读取dataSize个数据
  4. if(num ==0)break;//所有数据读完的条件,也是while结束的条件
  5. qsort(fp_unSortFile,dataSize,sizeof(int),compare);//然后排序
  6. WriteFile(fp_sortFile, each_file, dataSize);//接着分别写入file_num个文件夹
  7. }
2.第二步: 我们已经知道第一步结束后,每个文档都包含dataSize的已经排好的数据,接下来我们用指针数组分别指向每个文件的第一个元素(显然是最小的),取其中最小的元素并输出到最终排序的文档中,其中关键步骤还需要,读取完以后我们就要移动这个指针指向这个文档的下一个最小值:
  1. fscanf(fp_array[index],"%d",&first_data[index]);
  1. FILE**fp_array =newFILE*[file_num];//索引每个文件,先申请内存
  2. for(int i =0; i < file_num; i++)
  3. fscanf(fp_array[i],"%d",&first_data[i]);//这里将每个文件的第一个数字存储到first_data中
  4. while(true)
  5. {
  6. 所有文档都都没剩余的就终止
  7. int pMinData = first_data[index];
  8. findMinData(first_data,pMinData,index)//找到最小值,还要记得把这个值得index记录下来
  9. fscanf(fp_array[index],"%d",&first_data[index]);//移动index的最小指针
  10. }
 
核心代码:
  1. //快排并将结果放在磁盘中
  2. intMemorySort()
  3. {
  4. FILE* fp_unSortFile = fopen("d:\\unSort.txt","r");
  5. CheckFile(fp_unSortFile);
  6. int num;
  7. int count =0;//记录用快排后的文件数量
  8. while(true)
  9. {
  10. int*each_file =newint[dataSize];
  11. num=ReadFile( fp_unSortFile, each_file);
  12. if(num ==0)//终止条件,当读取的数字个数为0的时候,也就是全部读完以后就终止。
  13. break;
  14. count++;
  15. qsort(fp_unSortFile,dataSize,sizeof(int),compare);
  16. string new_name = new_file_name(count);
  17. FILE*fp_sortFile = fopen(new_name.c_str(),"w");//fopen 第一个参数类型是const char*,这里用c_str()进行转换
  18. WriteFile(fp_sortFile, each_file, dataSize);
  19. fclose(fp_sortFile);//记得关掉
  20. delete[]each_file;//记得释放
  21. }
  22. fclose(fp_unSortFile);
  23. return count;//返回文件数目
  24. }
  1. voidMergeSort(int file_num)
  2. {
  3. FILE*fp_sortFile = fopen("d:\\sortResult.txt","w");
  4. CheckFile(fp_sortFile);
  5. FILE**fp_array =newFILE*[file_num];//索引每个文件,先申请内存
  6. for(int i =0; i < file_num; i++)//理解怎么索引到每个文件的
  7. {
  8. string file_name = new_file_name(i +1);
  9. fp_array[i]= fopen(file_name.c_str(),"r");
  10. }
  11. int*first_data =newint[file_num];//要找到每个文件最小的数字,必须先把所有文件最小数字先保存起来
  12. for(int i =0; i < file_num; i++)
  13. fscanf(fp_array[i],"%d",&first_data[i]);//这里将每个文件的第一个数字存储到first_data中
  14. bool*finish =newbool[file_num];//主要用来保证所有的文件都读取完了
  15. memset(finish,false,sizeof(bool)*file_num);
  16. while(true)
  17. {
  18. //终止条件
  19. int index =0;
  20. while(index < file_num&&finish[index])
  21. index++;
  22. if(index >= file_num)
  23. break;
  24. int pMinData = first_data[index];
  25. for(int i = index +1; i < file_num; i++)
  26. {
  27. if(pMinData>first_data[i]&&(!first_data[index]))
  28. {
  29. pMinData = first_data[i];
  30. index = i;
  31. }
  32. }
  33. fprintf(fp_sortFile,"%d",pMinData);
  34. if(first_data[index]== EOF)//如果都读完了,那么这个文件就不用再读了。
  35. finish[index]=true;
  36. fscanf(fp_array[index],"%d",&first_data[index]);//这一句是代码的核心,每当将最小数存入fp_sortFile后,就将其读取指针移动一位。
  37. }
  38. }
其中的一些函数
  1. constint dataNumber =10000000;
  2. constint dataSize =250000;
  3. voidCheckFile(FILE* fp)
  4. {
  5. if(fp == NULL)
  6. {
  7. cout <<"打开文件出错!!!"<< endl;
  8. exit(1);
  9. }
  10. }
  11. intReadFile(FILE* fp,int*space)//将fp指向的数据读入space数组中,并返回读入的大小
  12. {
  13. int index =0;
  14. while(index<dataSize&&(fscanf(fp,"%d",&space[index])!= EOF))
  15. index++;
  16. return index;
  17. }
  18. voidWriteFile(FILE*fp,int*space,int num)//从space中将数据存入fp指向的文件,num限制写入的大小
  19. {
  20. int index =0;
  21. while(index<num)
  22. {
  23. fprintf(fp,"%d", space[index]);
  24. index++;
  25. }
  26. }
  27. int compare(constvoid*a,constvoid*b)//按照从小到大的顺序排列
  28. {
  29. return*(int*)a -*(int*)b;
  30. }
  31. string new_file_name(int count)
  32. {
  33. char name_buffer[20];
  34. sprintf(name_buffer,"data%d.txt",count);//注意sprintf 、fprintf区别,前者针对缓冲流,后者针对文件
  35. return name_buffer;
  36. }
 
总结:

1)、关于本章中位图和多路归并两种方案的时间复杂度及空间复杂度的比较,如下:

              时间复杂度       空间复杂度
位图         O(N)               0.625M
多位归并   O(Nlogn)        1M   

2)、bit-map

适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下
基本原理及要点:使用bit数组来表示某些元素是否存在,比如8位电话号码
扩展:bloom filter可以看做是对bit-map的扩展

问题实例:
1)已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。
2)2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。

将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map。

3)、[外排序适用范围]--归并排序

   大数据的排序,去重基本原理及要点:外排序的归并方法,置换选择败者树原理,最优归并树扩展。问题实例:1).有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16个字节,内存限制大小是1M。返回频数最高的100个词。这个数据具有很明显的特点,词的大小为16个字节,但是内存只有1m做hash有些不够,所以可以用来排序。内存可以当输入缓冲区使用。 

 参考:http://blog.csdn.net/v_JULY_v/article/details/6451990

 
 
 



来自为知笔记(Wiz)