5亿整数的大文件,怎么排?

题目和背景可以参看这里:http://weibo.com/p/1001603856172376577500 和 http://blog.jobbole.com/87600/

这里不妨明确下题目:给定一个大文件,内含5亿个整数,每个整数都属于1-9999999之间。请设计方案,对这些元素进行排序,并将排序结果写成文件输出。可用内存不超过2G。

注意到元素一共有5亿个,因此,文件中肯定存在重复元素,也就是说有些元素不止出现一次,因此基于朴素的bitmap并不能胜任此题。

由于lau叔提供的数据集下载太麻烦,因此自己手动生成了一个。

这里提供两个方案:

方案I:由于每个元素都不超过9999999,因此,可以考虑使用CountingSort(计数排序),统计每个元素出现的次数。如果文件中1出现三次,2出现三次,5出现两次,那么最后的结果就是11122255。对CountingSort的进一步介绍,可以参考《算法导论》

#include<iostream>
using namespace std;
const int MAX = 9999999;
int C[MAX + 1] = { 0 };
void countingSort()
{
    char filename_in[] = "E:\\in.txt";
    FILE *fp_in = fopen(filename_in, "r");
    char content[10];
    while (!feof(fp_in))
    {
        fgets(content,10, fp_in);
        int element = atoi(content);
        ++C[element]; //counting...
    }
    fclose(fp_in);
    //output
    char filename_out[] = "E:\\out.txt";
    FILE *fp_out = fopen(filename_out, "w");
    for (int i = 1; i <= MAX;i++)
    {
        for (int j = 1; j <= C[MAX]; j++)
        {
            fprintf(fp_out, "%d\n", i);
        }
    }
    fclose(fp_out);
}
int main()
{
    countingSort();
    system("pause");
    return 0;
}

方案II

我们可以使用k-路归并策略,也就是原文提到的外部排序。我们先按行读入文件,分批排序,输出k个临时的小文件,其中每个小文件都是有序的。这样,我们得到k个有序的小文件,问题转换为对这k个有序小文件的合并。

说明:

1,原文评论列表中,网友iduanyingjie 的评论 “ 外部排序的第2部分【合】的时候,没必要每次去取一个最小值。因为文件1,文件2,文件3每个文件中拿出的最小值,肯定是这三个文件中的最小的3个值了,将这三个值进行排序(1,2,3),直接写入大文件。第二回合中就直接取出的是(4,5,6) ”并不正确。考虑如下的三个有序小文件:

20 50 60

40 45 70

70 85 90

首选取出20、40、70,排序比较后输出最小的元素20,此时正确的做法是从第一个文件(也就是最小元素20所在的那个文件)读出50,然后从50、40、70中选出最小元素,也就是50,输出。接着从50所在的文件也就是第一个文件读出60,从60、40、70选出最小元素也就是40输出。以此类推。此时,并不能按照网友iduanyingjie  所说的简单的排序输出、排序输出。

因此,正确的做法是,读入k个元素,输出最小元素,再从最小元素所在的文件读下一个元素,继续选择最小元素输出;直到k个有序文件中的所有元素都处理完毕为止。

2,由于选择最小值是一个很关键的操作,因此可以使用包含k个元素的最小堆来高效的完成。整个过程就转换为:输出最小、替换堆顶、重建堆;输出最小、替换堆顶、重建堆......

3,当某个小文件的元素都处理完毕后,此时我们采取的策略是移动堆底部的元素到堆顶,并将堆大小减去1。这样,当堆大小为0的时候,说明所有的文件中的所有元素都处理完毕了。为了知道最小元素所在的文件,我们需要一个变量来记录文件号。

#include<iostream>
using namespace std;
const int K = 500;
struct Node
{
    int value;
    int file_id;
};
class Heap
{
public:
    Heap(int capacity)
    {
        this->capacity = capacity;
        this->size = 0;
        p = new Node[this->capacity + 1];
    }
    void buildMinHeap()
    {
        for (int i = size / 2; i >= 1; i--)
        {
            minHeapify(i);
        }
    }
    void insertNode(Node t)
    {
        size++;
        p[size].value = t.value;
        p[size].file_id = t.file_id;
    }
    Node getMin()
    {
        return p[1];
    }
    int getHeapSize()
    {
        return size;
    }
    void minHeapify(int i)
    {
        int left = 2 * i;
        int right = 2 * i + 1;
        int min = i;
        if (left <= size && p[left].value < p[i].value)
        {
            min = left;
        }
        if (right <= size && p[right].value < p[min].value)
        {
            min = right;
        }
        if (min != i)
        {
            Node tmp = p[i];
            p[i] = p[min];
            p[min] = tmp;
            minHeapify(min);
        }
    }
    void replaceRootNodeByLastNode()
    {
        p[1] = p[size];
        size--;
    }
    void replaceRootNodeByNextElement(Node n)
    {
        p[1] =n;
    }
    ~Heap()
    {
        delete[]p;
    }
private:
    Node *p;
    int size;
    int capacity;
};
Node getOneElement(FILE* & fp,int file_id)
{
    char content[10];
    Node ret;
    if(!feof(fp))
    {
        fgets(content, 10, fp);
        int element = atoi(content);
        ret.file_id = file_id;
        ret.value = element;
    }
    else
    {
        ret.value = -1;
    }
    return ret;
}
void writeResult(FILE* & fp, int element)
{
    fprintf(fp, "%d\n", element);
}
void kWayMergeViaHeap(FILE** fp)
{
    Heap *pHeap = new Heap(K);
    for (int i = 0; i <K; i++)
    {
        Node t = getOneElement(fp[i], i);
        pHeap->insertNode(t);
    }
    pHeap->buildMinHeap();
    char filename_output[] = "E:\\outT.txt";
    FILE *fp_out = fopen(filename_output, "w");
    while (pHeap->getHeapSize()>0)
    {
        Node minNode = pHeap->getMin();
        writeResult(fp_out, minNode.value);
        Node t = getOneElement(fp[minNode.file_id], minNode.file_id);
        if (t.value==-1) //there are no elements in fp[minNode.file_id]
        {
            pHeap->replaceRootNodeByLastNode();
        }
        else
        {
            pHeap->replaceRootNodeByNextElement(t);
        }
        pHeap->minHeapify(1);
    }
    //
    for (int i = 0; i <K; i++)
    {
        fclose(fp[i]);
    }
    fclose(fp_out);
    delete pHeap;
}

1,堆的实现大学时候写的,参照《算法导论》完成,尽管进一步的加速和优化是可能的。例如,minHeapify函数的末尾是一个尾递归,可以通过循环来代替。当然啦,开启编译器优化之后编译器可能会自动完成,因此2*i这类语句就真没必要用移位操作了。再例如,getOneElement中的接口设计,返回Node意味着需要构造函数开销,当然设计为只返回int更佳,但是后面就稍微麻烦了点。

2,堆是非常有用的一种数据结构,不仅可以用于k路合并,也可以用于k路求交(求k个有序链表/文件的交集),试试看。

3,对于输出,其实可以通过一个buffer保存一定量的元素之后,再一口气刷到硬盘上。麻烦,不写了。

4,为什么io部分用C,而其他部分用C++?这是因为C++的io实在是太慢了,慢的吓人。

时间: 2024-07-30 13:09:55

5亿整数的大文件,怎么排?的相关文章

大文件,5亿整数,怎么排?

问题 给你1个文件bigdata,大小4663M,5亿个数,文件中的数据随机,如下一行一个整数: 6196302 3557681 6121580 2039345 2095006 1746773 7934312 2016371 7123302 8790171 2966901 ... 7005375 现在要对这个文件进行排序,怎么搞? 内部排序 先尝试内排,选2种排序方式: 3路快排: private final int cutoff = 8; public <T> void perform(Co

对大文件排序

设想你有一个20GB的文件,每行一个字符串,说明如何对这个文件进行排序. 内存肯定没有20GB大,所以不可能采用传统排序法.但是可以将文件分成许多块,每块xMB,针对每个快各自进行排序,存回文件系统. 然后将这些块逐一合并,最终得到全部排好序的文件. 外排序的一个例子是外归并排序(External merge sort),它读入一些能放在内存内的数据量,在内存中排序后输出为一个顺串(即是内部数据有序的临时文件),处理完所有的数据后再进行归并.[1][2]比如,要对900MB的数据进行排序,但机器

Scribd每月共有超过两亿个访客、累积数亿篇以上的文件档案,Alexa全球排名200以内

目前已登上世界300大网站,每月共有超过两亿个访客.累积数亿篇以上的文件档案.透过Flash介面的阅读器-iPaper,使用者可以在网站内浏览各种文件,由于该网站是一个文件分享平台,所有的文件都是由使用者上传分享,所以你也可以在网站内找到各式各样的文件.例如在这里可以找到Scribd目前热门的文件一览. Scribd两年来取得巨大的成功.云科技收集到的数据包括:Alexa全球排名200以内,每月有5000万用户,每天有5万篇文档上传,共有5百万个内容被其它网站嵌套,涉及90种语言.据说,包括奥巴

内存映射大文件

对于一些小文件,用普通的文件流就可以很好的解决,可是对于超大文件,比如2G或者更多,文件流就不行了,所以要使用API的内存映射的相关方法,即使是内存映射,也不能一次映射全部文件的大小,所以必须采取分块映射,每次处理一小部分. 先来看几个函数 CreateFile :打开文件 GetFileSize : 获取文件尺寸 CreateFileMapping :创建映射 MapViewOfFile :映射文件 看MapViewOfFile的帮助,他的最后两个参数都需要是页面粒度的整数倍,一般机器的页面粒

内存映射对于大文件的使用

平时很少使用大文件的内存映射,碰巧遇到了这样的要求,所以把过程记录下来,当给各位一个引子吧,因为应用不算复杂,可能有考虑不到的地方,欢迎交流. 对于一些小文件,用普通的文件流就可以很好的解决,可是对于超大文件,比如2G或者更多,文件流就不行了,所以要使用API的内存映射的相关方法,即使是内存映射,也不能一次映射全部文件的大小,所以必须采取分块映射,每次处理一小部分. 先来看几个函数 CreateFile :打开文件 GetFileSize : 获取文件尺寸 CreateFileMapping :

BuzzSumo:什么样的文章能获得疯转?(基于1亿篇文章大数据分析)

BuzzSumo:什么样的文章能获得疯转?(基于1亿篇文章大数据分析) 社交媒体追踪服务分析工具BuzzSumo,2014年5月前后对社交媒体上超过1亿篇文章进行了分析,试图找出一个答案: 什么样的内容才能让用户乐于分享,获得病毒式传播? 这个大问题又内含或细分为一些小问题: ◆那些获得疯转的文章,激起了用户哪种情绪? ◆清单?图表?哪类文章更有可能被用户分享? ◆读者更喜欢分享短文章还是长文章?社交媒体上的文章,最理想的长度是怎样的? ◆“信任”是不是驱动用户分享文章的一个主要因素? ◆文章有

VC++ 中使用内存映射文件处理大文件

摘要: 本文给出了一种方便实用的解决大文件的读取.存储等处理的方法,并结合相关程序代码对具体的实现过程进行了介绍. 引言 文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile().WriteFile().ReadFile()和MFC提供的CFile类等.一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB.几百GB.乃至几TB的海量存储,再以通常的文件处理方法进行处

单表60亿记录等大数据场景的MySQL优化和运维之道 | 高可用架构(转)

转自http://www.php1.cn/Content/DanBiao_60_YiJiLuDengDaShuJuChangJingDe_MySQL_YouHuaHeYunWeiZhiDao_%7C_GaoKeYongJiaGou.html, 更多详细资料请参看原文 此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据存储平台建设和架构设计.前新浪高

PHP几个几十个G大文件数据统计并且排序处理

诸多大互联网公司的面试都会有这么个问题,有个4G的文件,如何用只有1G内存的机器去计算文件中出现次数最多的数字(假设1行是1个数组,例如QQ号 码).如果这个文件只有4B或者几十兆,那么最简单的办法就是直接读取这个文件后进行分析统计.但是这个是4G的文件,当然也可能是几十G甚至几百G的文 件,这就不是直接读取能解决了的. 同样对于如此大的文件,单纯用PHP做是肯定行不通的,我的思路是不管多大文件,首先要切割为多个应用可以承受的小文件,然后批量或者依次分析统计小文件后再把总的结果汇总后统计出符合要