如何在一个巨大的文本文件中对整数进行排序
How to sort integer numbers in a huge text file?
问题陈述
我得到了一个很大的数字列表,一次一个,我需要打印">中值"。
为了更清楚,可以有">125000000"个数字,并保证每个数字都小于"<em+1.e+18>"。
这是一场比赛,因此有内存限制(最多20 MB)和 时间限制(最多5秒)">中值"是位于排序数字中间的数字 排序编号后: ">中位数"应为16; 所以我在网上搜索了一下,但没有找到任何超过这些限制的答案。 我的方法是获取所有数字,将它们保存在文本文件中,对它们进行排序,然后获得">中值"。 因此,我想优化其中一个想法以通过限制,或者任何通过限制的新想法。 我更喜欢使用第二个想法,因为与其他两个想法不同,它通过了限制,但我不能这样做,因为我不知道如何在文本文件中间插入一行。所以,如果我学会了这一点,剩下的过程就会很容易。 这是一个接收数字的函数,通过读取文件,找到放置数字的最佳位置并将其放在那里
例如,如果这是数字列表:23
8
16
42
15
4
108
1) 4
2) 8
3) 15
4) 16
5) 23
6) 42
7) 108
方法
想法
但这将超过内存限制
这就像有一个循环,在得到下一个之后控制台中的数字读取文件(逐行)到达正确的位置,在那里插入数字,并且不接触其他数字
但问题是我不能在文本文件,因为它将覆盖其他数字
但是它花了太多时间,所以超过时间限制。请求
首选项
尝试的解决方案
事实上,它代表了我的第三个想法
所以它是有效的(我用大量的输入进行了测试),但我之前提到的问题是时间限制。void insertNewCombinedNumber ( int combinedNumber )
{
char combinedNumberCharacterArray[ 20 ];
bool isInserted = false;
ofstream combinedNumbersOutputFile;
ifstream combinedNumbersInputFile;
// Operate on First File
if ( isFirstCombinedFileActive )
{
combinedNumbersOutputFile.open ( "Combined Numbers - File 01.txt" );
combinedNumbersInputFile.open ( "Combined Numbers - File 02.txt" );
}
// Operate on Second File
else
{
combinedNumbersOutputFile.open ( "Combined Numbers - File 02.txt" );
combinedNumbersInputFile.open ( "Combined Numbers - File 01.txt" );
}
if ( !combinedNumbersInputFile )
{
combinedNumbersInputFile.close ();
ofstream combinedNumbersInputCreateFile ( "Combined Numbers - File 02.txt" );
combinedNumbersInputCreateFile.close ();
combinedNumbersInputFile.open ( "Combined Numbers - File 02.txt" );
}
combinedNumbersInputFile.getline ( combinedNumberCharacterArray , 20 );
for ( int i = 0; !combinedNumbersInputFile.eof (); i++ )
{
if ( !isInserted && combinedNumber <= characterArrayToDecimal ( combinedNumberCharacterArray ) )
{
combinedNumbersOutputFile << combinedNumber << endl;
isInserted = true;
}
combinedNumbersOutputFile << combinedNumberCharacterArray << endl;
combinedNumbersInputFile.getline ( combinedNumberCharacterArray , 20 );
}
if ( !isInserted )
{
combinedNumbersOutputFile << combinedNumber << endl;
isInserted = true;
}
isFirstCombinedFileActive = !isFirstCombinedFileActive;
combinedNumbersOutputFile.close ();
combinedNumbersInputFile.close ();
}
假设:
我假设数字列表已经是二进制形式的(因为我们需要多次通过数据,每次将文本转换为二进制都需要额外的处理时间)。这将是一个1GB(125M*64bit)的文件。
也不清楚该文件的操作系统磁盘缓存是否会计入内存限制。我认为不是,因为多次从磁盘冷读取1GB的文件已经需要5秒钟以上的时间。
解决方案:
因此,让我们从一个如何做到这一点的简单例子开始(我们稍后将对此进行优化和调整):
- 首先创建一个数字范围的直方图(例如100万组,但这还不起作用-见下文)
- 因此,创建一个大小为
max value / 1 million
(目前太大)的uint32
数组,我们将在其中放置bucket的计数(0-999999、100000-1999999,依此类推) - 循环浏览数字列表,每次递增数组的第n个值(数字所属的bucket)
- 既然我们有了一个计数数组,我们就可以很容易地计算出中值在哪个区间(或范围)
- 再次循环列表,现在只将符合该范围的数字存储在数组中
- 对数组进行排序,并计算哪一项是中值(同时使用所有存储桶的计数)
当然,我们需要对上面的内容进行一点调整。
首先,与其使用100万的范围,不如使用2的幂。这样,我们可以简单地使用带掩码的and
来获得bucket/计数列表中的位置(而不是使用更昂贵的除法)。
其次,对于使用范围为100万的bucket,我们必须创建一个太大的数组。
因此,最好的选择是进行3次传球:首先是1e12的范围,然后对于中值所在的范围,我们再次循环1e6的范围(但使用2的幂)。
这样,你只需要对属于一个小桶的数字进行排序,而不是对整个1.25亿的数字进行分类。排序需要O(n log n)
。
问题中给出的数字示例:
23
8
16
42
15
4
108
使用16个桶/范围-第一次通过:
array_pos count
0 (0-15) 3
1 (16-31) 2
2 (32-47) 1
3 (48-63) 0
4 (64-79) 0
5 (80-95) 0
6 (96-111) 1
我们现在可以计算出中值必须在array_pos
1的桶中。
remember/store these values:
Count before bucket 16-31: 3
Count after bucket 16-31: 2
第二次通过-读取bucket(16-31)的值-(同样,如果bucket大小是2的幂,我们可以使用一些位掩码来快速检查数字是否在该范围内):
23
16
对这个小数组进行排序,并使用2个计数(before
和after
)计算中值的位置。
count
3
16 -> median
23
2
您真正需要的是针对此类问题的分而治之算法。查看外部排序中的外部合并排序和分发排序部分
这个想法是将数据排序为多个块,然后使用分而治之的方法再次合并这些块。
它的时间复杂度为O(n-logn),我认为它将超过时间限制。
这些算法非常著名,你可以通过谷歌来获取实现细节。
在我的第一个答案中,我给出了一个解决方案,可以在二进制数列表或集合中找到中值(有内存限制),而不必对整个集合进行排序。
为了好玩,让我们看看一个解决方案,其中文件包含由换行符分隔的文本形式的数字,并且让我们在不将文本转换为二进制数字的情况下进行此操作(这可能很昂贵,而且我们无法将其保存在内存中)。
同样,我们将使用bucket(或bucket计数),但我们从按位数分组开始。
样本集:
1265
12
6548122
21516
6548455
516831213
2155
21158699
54866
第一次通过-按位数分组(array_pos
是本次的位数):
array_pos count
0 0
1 0
2 1
3 0
4 2
5 2
6 0
7 2
8 1
9 1
因此,中位数必须有5位数字(before: 3
-after:4
)。
第二次通过-(假设所有5位数都不适合20MB),读取所有5位数,并按第一位数(或前2、3或4,取决于计数)分组(计数):
first_digit count
1 0
2 1
3 0
4 0
5 1
(实际上,第二遍也可以在第一遍内完成,因为在这种情况下数组会很小(取决于我们分组的位数)。我们只需要为每个"数字数"创建一个数组)。
定位包含中位数的组:
count first_digit
3
1 2
1 5 -> median
4
最后一次通过-读取所有以5为第一位的5位数字,对它们进行排序(可以按字母顺序排列,仍然不需要转换),并定位中值(同样,我们只需要对数据的一小部分进行排序)。
在上面的小例子中,只有一个,但由于内存限制,我们没有存储结果,因此我们仍然必须将其保存在文件中。
出于性能原因,这里应该避免使用readline()
或streaming
之类的函数,而应该以二进制模式打开文件。通过这种方式,我们可以直接在字节上循环,并在遇到换行符时重置数字计数。
更好的方法是使用memory mapping
,但我想在这种情况下(限制为20GB)会作弊。
您可以尝试中值算法。它是一种时间复杂度为O(n)的就地算法
1.阅读此处
2。维基百科文章
- 文本文件中的单词链表
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 如何将内容数组写入文本文件?
- 无法通过空白将文本文件行分隔为矢量
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- C++将文本文件中的数据读取到结构数组中
- 在指针的帮助下,文本文件中单词的频率
- 将字符指针十六进制转换为字符串并保存在文本文件C++中
- 将值从二维数组输出到文本文件
- 如何使用 c++ 实现并发文件/文本编辑?
- 我需要转换一些代码,以便它适用于输入和输出文件文本
- 通过套接字发送二进制文件.文本文件有效,其他文件无效
- 将大型数据文件拆分为多个小文件(文本格式)
- 如何读取输入文件(文本文件)并将输入验证为有效整数
- 从文本文件/文本流中读取Q字符串错误
- 从文本文件(文本解析器)C++中读取行和列的逻辑
- 如何将*.css文件(文本文件)的内容与附加信息存储在新文件中?
- 关于内存映射文件和使用大文件文本编辑器
- 如何在读取文件文本时跳过"n"
- 使用libzip从.zip获取文件(文本除外)