externalsorting

来源:互联网 发布:体检软件哪个好 编辑:程序博客网 时间:2024/06/17 08:53

external sorting - python


                外部排序过程:
1) 将文件大小分块,并对每个分块进行快速排序,输出多个已经排序完成的小文件
2) 合并这些小文件,得到最终输出文件
假定源文件有M个记录,每个分块包含N个记录,其中 M >= N,我们将得到 upper_bound( M // N )个分块 (P)。 
1)时间复杂度:  O1 = N * lg(N) * P
2)时间复杂度:  O2 = N * P
总体时间复杂度: O = N * lg(N) * P + N * P
通常P def merge(A, B):
    C = []
    la = len(A)
    lb = len(B)
    ln = la + lb
    j = 0
    k = 0
    for i in range(ln):
        if j >= la:
            C.append(B)
            k += 1
            continue;
        if k >= lb:
            C.append(A)
            j += 1
            continue;
        if A > B:
            C.append(B)
            k += 1
        else:
            C.append(A)
            j += 1
    return C
我们将文件分成了P个部分,因此我们需要合并这P个记录,我们只需要将其中记录两两合并,递归一下即可。由于单词merge的复杂度为O(N),因此merge所有记录的复杂度为 O(N * P)
算法list_merge:
from mergesort import merge
def list_merge(T):
    ln = len(T)
    R = []
    if ln == 1:
        return T
    La = T.pop()
    Lb = T.pop()
    L = merge(La, Lb)
    T.append(L)
    return list_merge(T)
==>
def list_merge(T):
    return reduce(merge, T)
完整的主程序(esort.py):
注意输入文件格式为每行一个整数,纯文本格式。
from __future__ import with_statement
from quicksort import quicksort
from list_merge import list_merge
from os import unlink
"""
sort L, and output the result to file @op
"""
def blk_sort(L, op):
    quicksort(L)
    for x in L:
        op.write(str(x) + '\n')
"""
split @infile based on @threshold,
each splitted file is sorted seperately
return with the name list of splitted files
"""
def split_file(infile, threshold):
    n = 0
    nth = 0
    L = []
    outList = []
    with open(infile, "rt") as inp:
        for line in inp:
            if (n == 0):
                outfile = infile + '.out.' + str(nth)
                outList.append(outfile)
                print 'sorting ' + outfile
                try:
                    outp = open(outfile, "wb")
                except:
                    print "unable to write file " + outfile
                    return
            L.append(int(line))
            n += 1
            if n >= threshold:
                blk_sort(L, outp)
                n = 0
                nth += 1
                L = []
                outp.close()
    if n > 0:
        blk_sort(L, outp)
        outp.close()
    return outList
def _blk_merge(T):
    return list_merge(T)
def blk_merge(outFiles, threshold, outFinal):
    numFiles = len(outFiles)
    # how many records to fetch for each file
    records = threshold // numFiles
    # EOFed files
    eofFiles = 0
    exit_loop = False
    tmp = []
    T = []
    R = []
    opList = []
    ofp = open(outFinal, "wb")
    for f in outFiles:
        try:
            op = open(f, "rb")
            opList.append(op)
        except:
            print 'open file ' + f + ' error.'
            return;
    while not exit_loop:
            for o in opList:
                tmp = []
                for r in range(records):
                    try:
                        tmp.append(int(o.next()))
                    except StopIteration:
                        eofFiles += 1
                        if eofFiles >= numFiles:
                            exit_loop = True
                        break;
                T.append(tmp)
            R = _blk_merge(T)
            T = []
            for x in R:
                ofp.write(str(x) + '\n')
    ofp.close()
    for f in opList:
        f.close()
    for fname in outFiles:
        unlink(fname)
def esort(infile, threshold, outfile):
    outList = split_file(infile, threshold)
    blk_merge(outList, threshold, outfile)
if __name__ == '__main__':
    import sys
    argc = len(sys.argv)
    if argc != 4:
        print sys.argv + '   '
        sys.exit(1)
    infile = sys.argv
    threshold = int(sys.argv)
    outfile = sys.argv
    esort(infile, threshold, outfile)
对1M大小的随机数据文件,每个分块大小为100K和10K的情况:
$ time python ./esort.py 1m.dat 100000 output.dat  > /dev/null 
real    0m25.818s 
user    0m25.669s 
sys     0m0.122s 
$ time python ./esort.py 1m.dat 10000 output.dat  > /dev/null 
real    0m45.356s 
user    0m45.248s 
sys     0m0.098s 

$


第一节、如何给磁盘文件排序
问题描述:
输入:给定一个文件,里面最多含有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),程序可以用伪代码表示如下:

[cpp] view plain copy
 print?
  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  

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

[cpp] view plain copy
 print?
  1. //copyright@ yansha  
  2. //July、2010.05.30。  
  3. //位图方案解决10^7个数据量的文件的排序问题  
  4. //如果有重复的数据,那么只能显示其中一个 其他的将被忽略  
  5. #include <iostream>  
  6. #include <bitset>  
  7. #include <assert.h>  
  8. #include <time.h>  
  9. using namespace std;  
  10.   
  11. const int max_each_scan = 5000000;  
  12.   
  13. int main()  
  14. {  
  15.     clock_t begin = clock();  
  16.     bitset<max_each_scan> bit_map;  
  17.     bit_map.reset();  
  18.       
  19.     // open the file with the unsorted data  
  20.     FILE *fp_unsort_file = fopen("data.txt""r");  
  21.     assert(fp_unsort_file);  
  22.     int num;  
  23.   
  24.     // the first time scan to sort the data between 0 - 4999999  
  25.     while (fscanf(fp_unsort_file, "%d ", &num) != EOF)  
  26.     {  
  27.         if (num < max_each_scan)  
  28.             bit_map.set(num, 1);  
  29.     }  
  30.       
  31.     FILE *fp_sort_file = fopen("sort.txt""w");  
  32.     assert(fp_sort_file);  
  33.     int i;  
  34.       
  35.     // write the sorted data into file  
  36.     for (i = 0; i < max_each_scan; i++)  
  37.     {  
  38.         if (bit_map[i] == 1)  
  39.             fprintf(fp_sort_file, "%d ", i);  
  40.     }  
  41.       
  42.     // the second time scan to sort the data between 5000000 - 9999999  
  43.     int result = fseek(fp_unsort_file, 0, SEEK_SET);  
  44.     if (result)  
  45.         cout << "fseek failed!" << endl;  
  46.     else  
  47.     {  
  48.         bit_map.reset();  
  49.         while (fscanf(fp_unsort_file, "%d ", &num) != EOF)  
  50.         {  
  51.             if (num >= max_each_scan && num < 10000000)  
  52.             {  
  53.                 num -= max_each_scan;  
  54.                 bit_map.set(num, 1);  
  55.             }  
  56.         }  
  57.         for (i = 0; i < max_each_scan; i++)  
  58.         {  
  59.             if (bit_map[i] == 1)  
  60.                 fprintf(fp_sort_file, "%d ", i + max_each_scan);  
  61.         }  
  62.     }  
  63.       
  64.     clock_t end = clock();  
  65.     cout<<"用位图的方法,耗时:"<<endl;  
  66.     cout << (end - begin) / CLK_TCK << "s" << endl;  
  67.     fclose(fp_sort_file);  
  68.     fclose(fp_unsort_file);  
  69.     return 0;  
  70. }  

 而后测试了一下上述程序的运行时间,采取位图方案耗时14s,即14000ms:

本章中,生成大数据量(1000w)的程序如下,下文第二节的多路归并算法的c++实现和第三节的磁盘文件排序的编程实现中,生成的1000w数据量也是用本程序产生的,且本章内生成的1000w数据量的数据文件统一命名为“data.txt”。

[cpp] view plain copy
 print?
  1. //purpose:  生成随机的不重复的测试数据  
  2. //copyright@ 2011.04.19 yansha  
  3. //1000w数据量,要保证生成不重复的数据量,一般的程序没有做到。  
  4. //但,本程序做到了。  
  5. //July、2010.05.30。  
  6. #include <iostream>  
  7. #include <time.h>  
  8. #include <assert.h>  
  9. using namespace std;  
  10.   
  11. const int size = 10000000;  
  12. int num[size];  
  13.   
  14. int main()  
  15. {  
  16.     int n;  
  17.     FILE *fp = fopen("data.txt""w");  
  18.     assert(fp);  
  19.   
  20.     for (n = 1; n <= size; n++)    
  21.         //之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。  
  22.         num[n] = n;  
  23.     srand((unsigned)time(NULL));  
  24.     int i, j;  
  25.   
  26.     for (n = 0; n < size; n++)  
  27.     {  
  28.         i = (rand() * RAND_MAX + rand()) % 10000000;  
  29.         j = (rand() * RAND_MAX + rand()) % 10000000;  
  30.         swap(num[i], num[j]);  
  31.     }  
  32.   
  33.     for (n = 0; n < size; n++)  
  34.         fprintf(fp, "%d ", num[n]);  
  35.     fclose(fp);  
  36.     return 0;  
  37. }  

    不过很快,我们就将意识到,用此位图方法,严格说来还是不太行,空间消耗10^7/8还是大于1M(1M=1024*1024空间,小于10^7/8)。
    既然如果用位图方案的话,我们需要约1.25MB(若每条记录是8位的正整数的话,则10000000/(1024*1024*8) ~= 1.2M)的空间,而现在只有1MB的可用存储空间,那么究竟该作何处理呢?

updated && correct:

   @yansha: 上述的位图方案,共需要扫描输入数据两次,具体执行步骤如下:

  • 第一次,只处理1—4999999之间的数据,这些数都是小于5000000的,对这些数进行位图排序,只需要约5000000/8=625000Byte,也就是0.625M,排序后输出。
  • 第二次,扫描输入文件时,只处理4999999-10000000的数据项,也只需要0.625M(可以使用第一次处理申请的内存)。
    因此,总共也只需要0.625M

位图的的方法有必要强调一下,就是位图的适用范围为针对不重复的数据进行排序,若数据有重复,位图方案就不适用了。

    3、多路归并。诚然,在面对本题时,还可以通过计算分析出可以用如2的位图法解决,但实际上,很多的时候,我们都面临着这样一个问题,文件太大,无法一次性放入内存中计算处理,那这个时候咋办呢?分而治之,大而化小,也就是把整个大文件分为若干大小的几块,然后分别对每一块进行排序,最后完成整个过程的排序。k趟算法可以在kn的时间开销内和n/k的空间开销内完成对最多n个小于n的无重复正整数的排序。

    比如可分为2块(k=2,1趟反正占用的内存只有1.25/2M),1~4999999,和5000000~9999999。先遍历一趟,首先排序处理1~4999999之间的整数(用5000000/8=625000个字的存储空间来排序0~4999999之间的整数),然后再第二趟,对5000001~1000000之间的整数进行排序处理。在稍后的第二节、第三节、第四节,我们将详细阐述并实现这种多路归并排序磁盘文件的方案。
    4、读者思考。经过上述思路3的方案之后,现在有两个局部有序的数组了,那么要得到一个完整的排序的数组,接下来改怎么做呢?或者说,如果是K路归并,得到k个排序的子数组,把他们合并成一个完整的排序数组,如何优化?或者,我再问你一个问题,K路归并用败者树 和 胜者树 效率有什么差别?这些问题,请读者思考。

 

第二节、多路归并算法的c++实现

    本节咱们暂抛开咱们的问题,阐述下有关多路归并算法的c++实现问题。在稍后的第三节,咱们再来具体针对咱们的磁盘文件排序问题阐述与实现。

    在了解多路归并算法之前,你还得了解归并排序的过程,因为下面的多路归并算法就是基于这个流程的。其实归并排序就是2路归并,而多路归并算法就是把2换成了k,即多(k)路归并。下面,举个例子来说明下此归并排序算法,如下图所示,我们对数组8 3 2 6 7 1 5 4进行归并排序:

    归并排序算法简要介绍:
一、思路描述:
    设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中。
     
    二路归并排序的过程是:
    (1)把无序表中的每一个元素都看作是一个有序表,则有n个有序子表;
    (2)把n个有序子表按相邻位置分成若干对(若n为奇数,则最后一个子表单独作为一组),每对中的两个子表进行归并,归并后子表数减少一半;
    (3)反复进行这一过程,直到归并为一个有序表为止。

    二路归并排序过程的核心操作是将一维数组中相邻的两个有序表归并为一个有序表。

二、分类:
    归并排序可分为:多路归并排序、两路归并排序 。
    若归并的有序表有两个,叫做二路归并。一般地,若归并的有序表有k个,则称为k路归并。二路归并最为简单和常用,既适用于内部排序,也适用于外部排序。本文着重讨论外部排序下的多(K)路归并算法。

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

    好的,介绍完了归并排序后,回到咱们的问题。由第一节,我们已经知道,当数据量大到不适合在内存中排序时,可以利用多路归并算法对磁盘文件进行排序。

    我们以一个包含很多个整数的大文件为例,来说明多路归并的外排序算法基本思想。假设文件中整数个数为N(N是亿级的),整数之间用空格分开。首先分多次从该文件中读取M(十万级)个整数,每次将M个整数在内存中使用快速排序之后存入临时文件,然后使用多路归并将各个临时文件中的数据再次整体排好序后存入输出文件。显然,该排序算法需要对每个整数做2次磁盘读和2次磁盘写。以下是本程序的流程图:

    本程序是基于以上思想对包含大量整数文件的从小到大排序的一个简单实现,这里没有使用内存缓冲区,在归并时简单使用一个数组来存储每个临时文件的第一个元素。下面是多路归并排序算法的c++实现代码(在第四节,将给出多路归并算法的c实现): 

[cpp] view plain copy
 print?
  1. //copyright@ 纯净的天空 && yansha    
  2. //5、July,updated,2010.05.28。    
  3. #include <iostream>    
  4. #include <ctime>    
  5. #include <fstream>    
  6. //#include "ExternSort.h"using namespace std;    
  7. //使用多路归并进行外排序的类    
  8. //ExternSort.h    
  9. /** 大数据量的排序* 多路归并排序* 以千万级整数从小到大排序为例* 一个比较简单的例子,没有建立内存缓冲区*/    
  10. #ifndef EXTERN_SORT_H    
  11. #define EXTERN_SORT_H    
  12.   
  13. #include <cassert>class ExternSort    
  14. {    
  15. public:    
  16.     void sort()    
  17.     {    
  18.         time_t start = time(NULL);    
  19.         //将文件内容分块在内存中排序,并分别写入临时文件      
  20.         int file_count = memory_sort();    
  21.         //归并临时文件内容到输出文件    
  22.         merge_sort(file_count);    
  23.         time_t end = time(NULL);printf("total time:%f/n", (end - start) * 1000.0/ CLOCKS_PER_SEC);    
  24.     }    
  25.       
  26.     //input_file:输入文件名    
  27.     //out_file:输出文件名    
  28.     //count: 每次在内存中排序的整数个数    
  29.     ExternSort(const char *input_file, const char * out_file, int count)    
  30.     {    
  31.         m_count = count;    
  32.         m_in_file = new char[strlen(input_file) + 1];    
  33.         strcpy(m_in_file, input_file);    
  34.         m_out_file = new char[strlen(out_file) + 1];    
  35.         strcpy(m_out_file, out_file);    
  36.     }    
  37.     virtual ~ExternSort()    
  38.     {    
  39.         delete [] m_in_file;    
  40.         delete [] m_out_file;    
  41.     }    
  42. private:    
  43.     int m_count;     
  44.     //数组长度char *m_in_file;      
  45.     //输入文件的路径    
  46.     char *m_out_file;     
  47.     //输出文件的路径    
  48. protected:    
  49.     int read_data(FILE* f, int a[], int n)    
  50.     {    
  51.         int i = 0;    
  52.         while(i < n && (fscanf(f, "%d", &a[i]) != EOF))     
  53.             i++;    
  54.         printf("read:%d integer/n", i);    
  55.         return i;    
  56.     }    
  57.     void write_data(FILE* f, int a[], int n)    
  58.     {    
  59.         for(int i = 0; i < n; ++i)    
  60.             fprintf(f, "%d ", a[i]);    
  61.     }    
  62.     char* temp_filename(int index)    
  63.     {    
  64.         char *tempfile = new char[100];    
  65.         sprintf(tempfile, "temp%d.txt", index);    
  66.         return tempfile;    
  67.     }    
  68.     static int cmp_int(const void *a, const void *b)    
  69.     {    
  70.         return *(int*)a - *(int*)b;    
  71.     }    
  72.   
  73.     int memory_sort()    
  74.     {    
  75.         FILE* fin = fopen(m_in_file, "rt");    
  76.         int n = 0, file_count = 0;int *array = new int[m_count];    
  77.           
  78.         //每读入m_count个整数就在内存中做一次排序,并写入临时文件    
  79.         while(( n = read_data(fin, array, m_count)) > 0)    
  80.         {    
  81.             qsort(array, n, sizeof(int), cmp_int);   //这里,调用了库函数阿,在第四节的c实现里,不再调qsort。      
  82.             char *fileName = temp_filename(file_count++);    
  83.             FILE *tempFile = fopen(fileName, "w");    
  84.             free(fileName);    
  85.             write_data(tempFile, array, n);    
  86.             fclose(tempFile);    
  87.         }    
  88.         delete [] array;    
  89.         fclose(fin);    
  90.         return file_count;    
  91.     }    
  92.       
  93.     void merge_sort(int file_count)    
  94.     {    
  95.         if(file_count <= 0)     
  96.             return;    
  97.         //归并临时文件FILE *fout = fopen(m_out_file, "wt");    
  98.         FILE* *farray = new FILE*[file_count];    
  99.         int i;    
  100.         for(i = 0; i < file_count; ++i)    
  101.         {    
  102.             char* fileName = temp_filename(i);    
  103.             farray[i] = fopen(fileName, "rt");    
  104.             free(fileName);    
  105.         }    
  106.         int *data = new int[file_count];    
  107.         //存储每个文件当前的一个数字    
  108.         bool *hasNext = new bool[file_count];    
  109.         //标记文件是否读完    
  110.         memset(data, 0, sizeof(int) * file_count);    
  111.         memset(hasNext, 1, sizeof(bool) * file_count);    
  112.         for(i = 0; i < file_count; ++i)    
  113.         {    
  114.             if(fscanf(farray[i], "%d", &data[i]) == EOF)    
  115.                 //读每个文件的第一个数到data数组    
  116.                 hasNext[i] = false;    
  117.         }    
  118.   
  119.         while(true)    
  120.         {    
  121.             //求data中可用的最小的数字,并记录对应文件的索引    
  122.             int min = data[0];    
  123.             int j = 0;    
  124.             while (j < file_count && !hasNext[j])    
  125.                 j++;    
  126.             if (j >= file_count)      
  127.                 //没有可取的数字,终止归并    
  128.                 break;    
  129.             for(i = j + 1; i < file_count; ++i)    
  130.             {    
  131.                 if(hasNext[i] && min > data[i])    
  132.                 {    
  133.                     min = data[i];    
  134.                     j = i;    
  135.                 }    
  136.             }    
  137.             if(fscanf(farray[j], "%d", &data[j]) == EOF)     
  138.                 //读取文件的下一个元素    
  139.                 hasNext[j] = false;    
  140.             fprintf(fout, "%d ", min);    
  141.         }    
  142.   
  143.         delete [] hasNext;    
  144.         delete [] data;    
  145.         for(i = 0; i < file_count; ++i)    
  146.         {    
  147.             fclose(farray[i]);    
  148.         }  
  149.         delete [] farray;    
  150.         fclose(fout);    
  151.     }    
  152. };    
  153. #endif    
  154.   
  155. //测试主函数文件    
  156. /** 大文件排序* 数据不能一次性全部装入内存* 排序文件里有多个整数,整数之间用空格隔开*/    
  157.   
  158. const unsigned int count = 10000000;     
  159. // 文件里数据的行数const unsigned int number_to_sort = 1000000;     
  160. //在内存中一次排序的数量    
  161. const char *unsort_file = "unsort_data.txt";     
  162. //原始未排序的文件名    
  163. const char *sort_file = "sort_data.txt";     
  164. //已排序的文件名    
  165. void init_data(unsigned int num);     
  166.   
  167. //随机生成数据文件    
  168.   
  169. int main(int argc, char* *argv)    
  170. {    
  171.     srand(time(NULL));    
  172.     init_data(count);    
  173.     ExternSort extSort(unsort_file, sort_file, number_to_sort);    
  174.     extSort.sort();    
  175.     system("pause");    
  176.     return 0;    
  177. }    
  178.   
  179. void init_data(unsigned int num)    
  180. {    
  181.     FILE* f = fopen(unsort_file, "wt");    
  182.     for(int i = 0; i < num; ++i)    
  183.         fprintf(f, "%d ", rand());    
  184.     fclose(f);    
  185. }   

程序测试:读者可以继续用小文件小数据量进一步测试。

 

第三节、磁盘文件排序的编程实现

    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。 

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

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

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

其中,生成数据文件data.txt的代码在第一节已经给出。

程序测试

    1、咱们对1000W数据进行测试,打开半天没看到数据,

    2、编译运行上述程序后,data文件先被分成40个小文件data[1....40],然后程序再对这40个小文件进行归并排序,排序结果最终生成在result文件中,自此result文件中便是由data文件的数据经排序后得到的数据。

    3、且,我们能看到,data[i],i=1...40的每个文件都是有序的,如下图:

    4、最终的运行结果,如下,单位统一为ms:

    由上观之,我们发现,第一节的位图方案的程序效率是最快的,约为14s,而采用上述的多路归并算法的程序运行时间约为25s。时间主要浪费在读写磁盘IO上,且程序中用的库函数qsort也耗费了不少时间。所以,总的来说,采取位图方案是最佳方案。

小数据量测试:

    我们下面针对小数据量的文件再测试一次,针对20个小数据,每趟对4个数据进行排序,即5路归并,程序的排序结果如下图所示。

运行时间:

0ms,可以忽略不计了,毕竟是对20个数的小数据量进行排序:

沙海拾贝:

    我们不在乎是否能把一个软件产品或一本书最终完成,我们更在乎的是,在完成这个产品或创作这本书的过程中,读者学到了什么,能学到什么?所以,不要一味的马上就想得到一道题目的正确答案,请跟着我们一起逐步走向山巅。

第四节、多路归并算法的c实现

    本多路归并算法的c实现原理与上述c++实现一致,不同的地方体现在一些细节处理上,且对临时文件的排序,不再用系统提供的快排,即上面的qsort库函数,是采用的三数中值的快速排序(个数小于3用插入排序)的。而我们知道,纯正的归并排序其实就是比较排序,在归并过程中总是不断的比较,为了从两个数中挑小的归并到最终的序列中。ok,此程序的详情请看:

[cpp] view plain copy
 print?
  1. //copyright@ 555  
  2. //July、2011.05.29。  
  3. #include <assert.h>  
  4. #include <time.h>   
  5. #include <stdio.h>     
  6. #include <memory.h>  
  7. #include <stdlib.h>  
  8.   
  9. void swap_int(int* a,int* b)  
  10. {      
  11.     int c;      
  12.     c = *a;      
  13.     *a = *b;      
  14.     *b = c;  
  15. }  
  16.   
  17. //插入排序  
  18. void InsertionSort(int A[],int N)  
  19. {      
  20.     int j,p;      
  21.     int tmp;     
  22.     for(p = 1; p < N; p++)      
  23.     {         
  24.         tmp = A[p];  
  25.         for(j = p;j > 0 && A[j - 1] >tmp;j--)          
  26.         {              
  27.             A[j] = A[j - 1];          
  28.         }         
  29.           
  30.         A[j] = tmp;     
  31.     }  
  32. }  
  33.   
  34. //三数取中分割法  
  35. int Median3(int A[],int Left,int Right)  
  36. {  
  37.     int Center = (Left + Right) / 2;  
  38.     if (A[Left] > A[Center])  
  39.         swap_int(&A[Left],&A[Center]);  
  40.     if (A[Left] > A[Right])  
  41.         swap_int(&A[Left],&A[Right]);  
  42.     if (A[Center] > A[Right])  
  43.         swap_int(&A[Center],&A[Right]);  
  44.     swap_int(&A[Center],&A[Right - 1]);  
  45.     return A[Right - 1];  
  46. }  
  47.   
  48. //快速排序  
  49. void QuickSort(int A[],int Left,int Right)  
  50. {  
  51.     int i,j;  
  52.     int Pivot;  
  53.     const int Cutoff = 3;  
  54.     if (Left + Cutoff <= Right)  
  55.     {  
  56.         Pivot = Median3(A,Left,Right);  
  57.         i = Left;  
  58.         j = Right - 1;  
  59.         while (1)  
  60.         {  
  61.             while(A[++i] < Pivot){;}  
  62.             while(A[--j] > Pivot){;}  
  63.             if (i < j)  
  64.                 swap_int(&A[i],&A[j]);  
  65.             else  
  66.                 break;  
  67.         }  
  68.         swap_int(&A[i],&A[Right - 1]);   
  69.           
  70.         QuickSort(A,Left,i - 1);  
  71.         QuickSort(A,i + 1,Right);  
  72.     }  
  73.     else  
  74.     {  
  75.         InsertionSort(A+Left,Right - Left + 1);  
  76.     }  
  77. }  
  78.   
  79. //const int  KNUM  = 40;          
  80. //分块数  
  81. const int  NUMBER = 10000000;   
  82. //输入文件最大读取的整数的个数  
  83. //为了便于测试,我决定改成小文件小数据量进行测试。  
  84. const int  KNUM  = 4;          
  85. //分块数const int  NUMBER = 100;   
  86. //输入文件最大读取的整数的个数  
  87. const char *in_file = "infile.txt";  
  88. const char *out_file = "outfile.txt";  
  89. //#define OUTPUT_OUT_FILE_DATA  
  90. //数据量大的时候,没必要把所有的数全部打印出来,所以可以把上面这句注释掉。  
  91. void  gen_infile(int n)  
  92. {  
  93.     int i;  
  94.     FILE *f = fopen(in_file, "wt");   
  95.     for(i = 0;i < n; i++)  
  96.         fprintf(f,"%d ",rand());  
  97.     fclose(f);  
  98. }  
  99.   
  100. int  read_data(FILE *f,int a[],int n)  
  101. {  
  102.     int i = 0;  
  103.     while ((i < n) && (fscanf(f,"%d",&a[i]) != EOF))    
  104.         i++;  
  105.     printf("read: %d integer/n",i);  
  106.     return i;  
  107. }  
  108.   
  109. void  write_data(FILE *f,int a[],int n)  
  110. {  
  111.     int i;for(i = 0; i< n;i++)  
  112.         fprintf(f,"%d ",a[i]);  
  113. }  
  114.   
  115. char* temp_filename(int index)  
  116. {  
  117.     char *tempfile = (char*) malloc(64*sizeof(char));  
  118.     assert(tempfile);  
  119.     sprintf(tempfile, "temp%d.txt", index);  
  120.     return tempfile;  
  121. }  
  122.   
  123. //K路串行读取  
  124. void k_num_read(void)  
  125. {  
  126.     char* filename;  
  127.     int i,cnt,*array;  
  128.     FILE* fin;  
  129.     FILE* tmpfile;  
  130.     //计算knum,每路应读取的整数个数int n = NUMBER/KNUM;  
  131.     if (n * KNUM < NUMBER)n++;  
  132.   
  133.     //建立存储分块读取的数据的数组  
  134.     array = (int*)malloc(n * sizeof(int));assert(array);  
  135.     //打开输入文件  
  136.     fin = fopen(in_file,"rt");  
  137.     i = 0;  
  138.       
  139.     //分块循环读取数据,并写入硬盘上的临时文件  
  140.     while ( (cnt = read_data(fin,array,n))>0)  
  141.     {  
  142.         //对每次读取的数据,先进行快速排序,然后写入硬盘上的临时文件  
  143.         QuickSort(array,0,cnt - 1);  
  144.         filename = temp_filename(i++);  
  145.         tmpfile = fopen(filename,"w");  
  146.         free(filename);  
  147.         write_data(tmpfile,array,cnt);  
  148.         fclose(tmpfile);  
  149.     }  
  150.     assert(i == KNUM);  
  151.     //没有生成K路文件时进行诊断  
  152.     //关闭输入文件句柄和临时存储数组  
  153.     fclose(fin);  
  154.     free(array);  
  155. }  
  156.   
  157. //k路合并(败者树)  
  158. void k_num_merge(void)  
  159. {  
  160.     FILE *fout;  
  161.     FILE **farray;  
  162.     char *filename;  
  163.     int  *data;  
  164.     char *hasNext;  
  165.     int i,j,m,min;  
  166. #ifdef OUTPUT_OUT_FILE_DATAint id;  
  167. #endif  
  168.     //打开输出文件  
  169.     fout = fopen(out_file,"wt");  
  170.     //打开各路临时分块文件  
  171.     farray = (FILE**)malloc(KNUM*sizeof(FILE*));  
  172.     assert(farray);  
  173.     for(i = 0; i< KNUM;i++)  
  174.     {  
  175.         filename = temp_filename(i);  
  176.         farray[i] = fopen(filename,"rt");  
  177.         free(filename);  
  178.     }  
  179.       
  180.     //建立KNUM个元素的data,hasNext数组,存储K路文件的临时数组和读取结束状态  
  181.     data = (int*)malloc(KNUM*sizeof(int));  
  182.     assert(data);  
  183.     hasNext = (char*)malloc(sizeof(char)*KNUM);  
  184.     assert(hasNext);  
  185.     memset(data, 0, sizeof(int) * KNUM);  
  186.     memset(hasNext, 1, sizeof(char) * KNUM);  
  187.       
  188.     //读K路文件先读取第一组数据,并对读取结束的各路文件设置不可再读状态  
  189.     for(i = 0; i < KNUM; i++)  
  190.     {  
  191.         if(fscanf(farray[i], "%d", &data[i]) == EOF)  
  192.         {  
  193.             hasNext[i] = 0;  
  194.         }  
  195.     }  
  196.       
  197.     //读取各路文件,利用败者树从小到大输出到输出文件  
  198. #ifdef OUTPUT_OUT_FILE_DATAid = 0;  
  199. #endif  
  200.       
  201.     j  = 0;F_LOOP:  
  202.     if (j < KNUM)      
  203.         //以下这段代码嵌套过深,日后应尽量避免此类问题。  
  204.     {  
  205.         while(1==1)  
  206.         {  
  207.             min = data[j];  
  208.             m = j;  
  209.             for(i = j+1; i < KNUM; i++)  
  210.             {  
  211.                 if(hasNext[i] == 1  && min > data[i])  
  212.                 {  
  213.                     min = data[i];m = i;  
  214.                 }  
  215.             }  
  216.   
  217.             if(fscanf(farray[m], "%d", &data[m]) == EOF)   
  218.             {  
  219.                 hasNext[m] = 0;  
  220.             }  
  221.             fprintf(fout, "%d ", min);  
  222. #ifdef OUTPUT_OUT_FILE_DATAprintf("fout :%d  %d/n",++id,min);  
  223. #endif  
  224.             if (m == j && hasNext[m] == 0)  
  225.             {  
  226.                 for (i = j+1; i < KNUM; i++)  
  227.                 {  
  228.                     if (hasNext[m] != hasNext[i])  
  229.                     {  
  230.                         m = i;  
  231.                         //第i个文件未读完,从第i个继续往下读  
  232.                         break;  
  233.                     }  
  234.                 }  
  235.                 if (m != j)  
  236.                 {  
  237.                     j = m;  
  238.                     goto F_LOOP;  
  239.                 }  
  240.                 break;  
  241.             }  
  242.         }  
  243.     }  
  244.       
  245.     //关闭分配的数据和数组      
  246.     free(hasNext);     
  247.     free(data);         
  248.     for(i = 0; i < KNUM; ++i)     
  249.     {          
  250.         fclose(farray[i]);     
  251.     }     
  252.     free(farray);      
  253.     fclose(fout);  
  254. }  
  255.   
  256. int main()      
  257. {     
  258.     time_t start = time(NULL),end,start_read,end_read,start_merge,end_merge;  
  259.     gen_infile(NUMBER);      
  260.     end = time(NULL);     
  261.     printf("gen_infile data time:%f/n", (end - start) * 1000.0/ CLOCKS_PER_SEC);  
  262.     start_read = time(NULL);k_num_read();      
  263.     end_read = time(NULL);     
  264.     printf("k_num_read time:%f/n", (end_read - start_read) * 1000.0/ CLOCKS_PER_SEC);  
  265.     start_merge = time(NULL);  
  266.     k_num_merge();      
  267.     end_merge = time(NULL);      
  268.     printf("k_num_merge time:%f/n", (end_merge - start_merge) * 1000.0/ CLOCKS_PER_SEC);     
  269.     end = time(NULL);     
  270.     printf("total time:%f/n", (end - start) * 1000.0/ CLOCKS_PER_SEC);      
  271.     return 0;    
  272. }    

程序测试:

在此,我们先测试下对10000000个数据的文件进行40趟排序,然后再对100个数据的文件进行4趟排序(读者可进一步测试)。如弄几组小点的数据,输出ID和数据到屏幕,再看程序运行效果。

  1. 10个数, 4组
  2. 40个数, 5组
  3. 55个数, 6组
  4. 100个数, 7组

 

(备注:1、以上所有各节的程序运行环境为windows xp + vc6.0 + e5200 cpu 2.5g主频,2、感谢5为本文程序所作的大量测试工作)

全文总结:

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

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

(多路归并,时间复杂度为O(k*n/k*logn/k ),严格来说,还要加上读写磁盘的时间,而此算法绝大部分时间也是浪费在这上面)

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有些不够,所以可以用来排序。内存可以当输入缓冲区使用。 

4、海量数据处理

    有关海量数据处理的方法或面试题可参考此文,十道海量数据处理面试题与十个方法大总结。日后,会逐步实现这十个处理海量数据的方法。同时,送给各位一句话,解决问题的关键在于熟悉一个算法,而不是某一个问题。熟悉了一个算法,便通了一片题目。

本章完。

    updated:有一读者朋友针对本文写了一篇文章为,海量数据多路归并排序的c++实现(归并时利用了败者树),地址为:http://www.cnblogs.com/harryshayne/archive/2011/07/02/2096196.html。谢谢,欢迎参考。


    版权所有,本人对本blog内所有任何内容享有版权及著作权。网络转载,请以链接形式注明出处。
2
1
 
 
猜你在找
iOS8-Swift开发教程
软件测试基础
数据结构和算法
老郭全套iOS开发课程【Objective-C】
微信公众平台开发入门
如何给107个数据量的磁盘文件排序
给107个数据量的磁盘文件排序
如何给107个数据量的磁盘文件排序
如何给107个数据量的磁盘文件排序
如何给107个数据量的磁盘文件排序
查看评论
81楼 hlj_ljz 2014-04-02 15:40发表 [回复]
lz能否解释下在生成大数据那段
i = (rand() * RAND_MAX + rand()) % 10000000;
这行代码什么意思,为什么我运行的时候数组总是越界呢,直接
i=rand()%10000000不行么?
Re: xintao901029 2014-05-16 11:44发表 [回复]
回复hlj_ljz:rand()产生的随机数范围是0-2^16-1,不能到10000000
Re: xintao901029 2014-05-16 11:46发表 [回复]
回复xintao901029:数组越界是他代码本身有个边界问题没处理好
int num[size];  
for (n = 1; n <= size; n++)  
//之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。  
num[n] = n; //此处发生越界
80楼 cxmmjj 2014-03-12 16:17发表 [回复]
第一次,只处理1—4999999之间的数据,第二次处理4999999-10000000的数据项,位图设置的数组会溢出的。 bitset<max_each_scan> bit_map;  
bit_map.reset();  
第一次相当于没有扫描完,第二次多一位。
已经测试过了
79楼 yujiazhen1991 2013-12-29 19:43发表 [回复]
你好,在第三节、磁盘文件排序的编程实现的内存排序中,只对250k的数据进行排序,250k是只要在1M的一定范围内还是有特定目的?
78楼 cyneuzk 2013-09-20 16:07发表 [回复]
您好,第三节程序的归并中,在比较个文字文件的当前最小数之后,应该改变index的值。我看您的程序里把它注释掉了
77楼 Maverick_87 2013-09-07 23:02发表 [回复]
测试了一下C语言班的归并排序,用生成不重复整数的程序,生成了100个整数,想分割成10个文件,最后的result文件的数据很多事重复的。
Re: v_JULY_v 2013-09-08 08:27发表 [回复]
回复Maverick_87:你好,你测试的具体是哪一节的程序?
Re: Maverick_87 2013-09-09 01:41发表 [回复]
回复v_JULY_v:没有用类的版本
//copyright@ yansha  
//July、updated,2011.05.28。  
#include <iostream>  
#include <string>  
#include <algorithm>  
#include <time.h>  
using namespace std;  

int sort_num = 10000000;  
int memory_size = 250000;  

//每次只对250k个小数据量进行排序  
int read_data(FILE *fp, int *space)  
{  
int index = 0;  
while (index < memory_size && fscanf(fp, "%d ", &space[index]) != EOF)  
index++;  
return index;  
}  

void write_data(FILE *fp, int *space, int num)  
{  
int index = 0;  
while (index < num)  
{  
fprintf(fp, "%d ", space[index]);  
index++;  
}  
}
76楼 踏雁寻花 2013-09-01 23:50发表 [回复]
您好,我想请教您一个为问题可以吗?
如果我用java编写的话,我如何查看应用程序所用的内存呢?
限制在1MB的内存空间,我又如何判断呢?
75楼 yankai0219 2013-08-30 14:35发表 [回复]
关于这个题目中K路归并时所采用的算法。在您的原文中,K路归并时就是采用数组比较获得最小值,复杂度为O(n*k)。http://blog.csdn.net/wypblog/article/details/8074831采用败者树与胜者树来完成有序数组的排序,其时间复杂度是n*logk,其中k为有序数组的个数,n为所有有序数组个数之和。 如果采用最小堆或者最大堆来完成归并,其时间复杂度同样是n*logk.但是空间上的话,最大堆与最小堆 只需要维持一个k大小的数组,而败者树与胜者树却要维持一个2*k大小的数组。我觉得采用最大堆或最小堆能够更好的完成K路归并。但是为什么没有进行考虑呢?
74楼 bzbrady 2013-08-15 09:58发表 [回复]
大牛啊
73楼 ilogo 2013-08-14 21:51发表 [回复]
还可以用B-树什么的,貌似有点杀鸡用牛刀了。。
72楼 ilogo 2013-08-14 21:48发表 [回复]
可以先扫描原始文件一次,把数扔到N个已经有序的文件中(1,2,。。n,这个n不要大了),排序每个文件就行了,可能I/O时间会有所增加。分布式环境下,全局排序的思想也是借鉴这个吧。。如有雷同,勿拍啊!
71楼 wavetry 2013-08-02 16:53发表 [回复]
看不太懂 膜拜大牛
70楼 keary093 2013-05-06 16:08发表 [回复]
请问可否使用基数排序来解决这个问题?
大致思路是:先每次从unsort_data.txt中读取1M的数字到内存中,然后根据个位数排序,将排好序的1M数字分别存入磁盘上的0.txt,1.txt……9.txt当中。个位排完序后,再从0.txt开始一直至9.txt,每次仍读取1M大小的数字至内存,根据十位数排序,排好序后存入1_0.txt,1_1.txt…1_9.txt中。排完后0.txt至9.txt可以删除。以此类推~
基数排序的时间复杂度是O(dn),在此题中为O(7*n),即O(n),比归并要快,所以我认为这个可能可行~
69楼 lij0526 2013-03-09 18:20发表 [回复]
还有,楼主在程序中大量使用了fopen等函数,需要引用头文件stdio.h
68楼 lij0526 2013-03-09 18:11发表 [回复]
你好,我看了您生成1000万个测试数据的程序,感觉在i和j这会不会溢出呢?
i和j都是int,这样一处理后有可能变成负值,导致后面的swap中数组取值时出现越界。
67楼 kjs008 2012-12-05 15:41发表 [回复]
1.位图法内存需要1.25M,需要结合hash读2次。
2.编程珠玑上面的快速排序法读了40次的原始数据,应该可以用类似与桶排序的方法,hash到40个临时文件。读2次就可以了。
66楼 GeekStuff 2012-12-03 16:26发表 [回复]
将多路归并排序倒过来,进行分布式排序,不过效率比多路归并差一点。
最大堆最小堆是可以的,只不过要将树进行修改,改为外存上的数据结构,将每个内节点加上缓冲区。复杂度和多路归并排序一样,但具体的实验结果要比多路归并差一点。
65楼 kuaiwei2005 2012-11-29 17:55发表 [回复]
for (n = 1; n <= size; n++)  
//之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。  
num[n] = n;  
数组下表越界了,应该是 
for (n = 0; n < size; n++)
num[n] = n + 1;
64楼 CPPAlien 2012-10-09 09:10发表 [回复]
生成随机数文件中的
for(n = 1;n <= size;n ++)
num[n] = n;
这样写是不是数组越界了,
63楼 SKATE11 2012-09-15 00:16发表 [回复]
楼主是哪个学校的 现在哪工作啊 真心崇拜你
62楼 titer1 2012-08-17 11:33发表 [回复]
我将第二节、多路归并算法的c++实现 的
程序实际运行了下,
我想测试最多可以分成多少个文件,//文件的极限
结果报fscanf错误,

详细帖子在
http://topic.csdn.net/u/20120817/11/5907c6a3-83c8-47e4-b806-5ea55c75d82d.html
61楼 v_JULY_v 2012-07-23 00:04发表 [回复]
感谢風過無痕的来信,谢谢:
hi
july,看到你的博客《程序员编程艺术:第十章、如何给10^7个数据量的磁盘文件排序》,下面回复一些人说生成测
试数据那段代码有问题,就写了个,测试了10、100、1000个不重复的随机数的生成,应该没有问题,现把代码发给你。祝:工作顺利
  1. //生成随机的不重复的测试数据  
  2. #include <iostream>  
  3. #include <time.h>  
  4. #include <assert.h>  
  5. using namespace std;  
  6. //产生[i,u]区间的随机数  
  7. int randint(int l, int u)  
  8. {  
  9.  return l+(RAND_MAX*rand()+rand())%(u-l+1);  
  10. }  
  11.  //1000W的int,大约4M的数据,如果放在mian内,在我的机子上好像是栈溢出了,放在全局空间就没问题  
  12. const int size = 10000000;  
  13. int num[size];  
  14. int main()  
  15. {  
  16.     int i, j;  
  17.     FILE *fp = fopen("data.txt""w");  
  18.     assert(fp);  
  19.     for (i = 0; i < size; i++)  
  20.         num[i] = i+1;  
  21.     srand((unsigned)time(NULL));  
  22.     for (i = 0; i < size; i++)  
  23.     {  
  24.         j = randint(i, size-1);  
  25.         int t = num[i]; num[i] = num[j]; num[j] = t;  
  26.         //swap(num[i], num[j]);  
  27.     }  
  28.     for (i = 0; i < size; i++)  
  29.         fprintf(fp, "%d ", num[i]);  
  30.     fclose(fp);  
  31.     return 0;  
  32. }  
Re: SuperFC 2012-07-23 17:04发表 [回复]
回复v_JULY_v:洗牌算法!
60楼 luxiaoxun 2012-07-21 14:42发表 [回复]
回复v_JULY_v:string new_file_name(int n) 

char file_name[20]; 
sprintf(file_name, "data%d.txt", n); 
return file_name; 
}
代码没有问题,返回的是string,函数返回前调用string的拷贝构造函数用file_name生成了string
59楼 yishengjun2025 2012-05-22 15:35发表 [回复]
第四节 k_num_merge对多路归并运用败者树的部分,好像没有用到败者树.(ps:编程艺术我觉得挺好的)
58楼 cys1991 2012-05-05 22:18发表 [回复]
兄弟 你什么时候开始研究算法的啊 感觉你研究的不错呀
57楼 盖世天才 2012-05-05 16:55发表 [回复]
string new_file_name(int n)  
{  
char file_name[20];  
sprintf(file_name, "data%d.txt", n);  
return file_name;  
} 这段代码不对吧?数组是存在栈区的,函数调用返回,栈区空间随时会被收回。。。应该改成指针,用的是堆区内存,就没问题了
56楼 hpghy123456 2012-05-05 14:46发表 [回复]
什么时候出本书呀!强烈建议
55楼 qianmin_ 2012-04-25 11:32发表 [回复]
  1. //外排有错  
  2.  int min = data[0];    //改为min=MAX_INT  
  3.             int j = 0;      
  4.             while (j < file_count && !hasNext[j])      
  5.                 j++;      
  6.             if (j >= file_count)          
  7.                 break;      
  8.             for(i = j + 1; i < file_count; ++i)  //改为i=j    
  9.             {      
  10.                 if(hasNext[i] && min > data[i])      
  11.                 {      
  12.                     min = data[i];      
  13.                     j = i;      
  14. /*外排这一段实现是错的,当只剩最后一个文件还有数据的时//候,就不会进入该if语句,这时每次读入的就是data[0]*/  
  15.                 }      
  16.             }     
  17.             /*增加 min=data[j],在只剩最后一个文件是也能保证全部输出 */  
  18.             if(fscanf(farray[j], "%d", &data[j]) == EOF)         
  19.                 hasNext[j] = false;      
  20.             fprintf(fout, "%d ", min);     
Re: luxiaoxun 2012-07-21 14:44发表 [回复]
回复qianmin_:C语言版的应该没有问题,测试过了
54楼 qianmin_ 2012-04-24 23:02发表 [回复]
for (n = 1; n <= size; n++)  
//之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。  
num[n] = n;  

这里明显有错吧,最大能到num[size]=size,不溢出了吗。
Re: v_JULY_v 2012-07-23 00:05发表 [回复]
回复qianmin_:具体哪一节哪一段代码?
53楼 zhaochengliang123 2012-03-23 16:31发表 [回复]
跪拜
52楼 michaelscofielddong 2011-12-14 21:41发表 [回复]
50楼说的对。
51楼 zhoujiealex 2011-11-25 10:16发表 [回复]
为什么评论没了
50楼 zhoujiealex 2011-11-25 10:15发表 [回复]
for (n = 1; n <= size; n++)  
//之前此处写成了n=0;n<size。导致下面有一段小程序的测试数据出现了0,特此订正。  
num[n] = n; 


这里初始化:num[0]没用吗? 还有 num[size]=size; 越界了。 
看你的注释,是故意这么做的? 这么多人没提出来。我都怀疑自己问题了。。。。。。。。。。
这里int num[size]在全局区分配了空间,没问题。所以运行没有出现stack corrupt。 如果num在函数里(这里栈不够,纯虚为了说明越界),就会出现问题了。
49楼 Gravitoon 2011-11-08 16:44发表 [回复]
K路归并时,可以使用小顶堆来稍微提升运行速度。即:每次取走堆顶数据,同一文件的后续数据入堆,维护堆。为什么这么想?因为K路归并在CLRS里面是讲完堆之后的课后习题之一...
48楼 whspecial 2011-10-30 14:01发表 [回复]
按照作者的意思,是不是在做二路归并外部排序的时候,每次从两个文件中读出两个数字,比较大小,将较小的那个写入新文件?这样的话,频繁的读写磁盘,开销会较大。
我想到的是可以一次读入内存一半大小的数据,例如内存大小为10M,两个待排序的文件是1G,两个文件都是有序的。从文件A读出5M数字,从文件B读出5M数字,在内存中进行排序后,将前5M数字一次性写入结果文件;再从文件A读出5M数字,与前面内存中剩余的5M数字一起排序,将前5M数字一次性写入结果文件,这样循环下去直到处理完毕。。。
不知道大家对这个方法有什么看法?
47楼 lzc52151 2011-10-08 22:29发表 [回复]
如果对磁盘I/O读写有了解的话,稍加利用效率会更好。1G的文件排序可以两分半左右。
Re: v_JULY_v 2011-10-08 22:32发表 [回复]
回复lzc52151:多谢指教。
46楼 parakpurple 2011-09-09 21:35发表 [回复] [引用] [举报]
代码贴得太乱了

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 错过法宣考试怎么办 两套房改房不退怎么办 孩子初一政治差怎么办 学生上课老说话怎么办 一年级语文太差怎么办 二年级成绩不好怎么办 初中学生数学差怎么办 孩子数学计算能力差怎么办 股票没有客户号怎么办 五岁宝宝鼻炎怎么办 孩子怕老师家长怎么办 孩子得了厌学症怎么办 幼儿不好好吃饭怎么办 孩子在学校胆小怎么办 孩子胆小没自信怎么办 特别倔强的学生怎么办 初中生注意力不集中怎么办 父母水平太低怎么办 父母不肯买电脑怎么办 与父母性格不合怎么办 父母和孩子吵架怎么办 如果有孩子离婚怎么办 离婚时成年孩子怎么办 父母离婚后孩子怎么办 父母吵架闹离婚怎么办 父母吵架后冷战怎么办 家里每天都吵架怎么办 夫妻因为钱吵架怎么办 离婚了很痛苦怎么办 身份证丢了怎么办离婚 和老婆离婚了怎么办 老婆跟前任联系怎么办 离异小孩上户口怎么办 离婚之后孩子户口怎么办 父母离婚孩子户口怎么办 夫妻离婚孩子户口怎么办 孩子有心理阴影怎么办 孩子心里有障碍怎么办 初中的孩子厌学怎么办 孩子抑郁了家长怎么办 大人得地图舌怎么办