对一亿个整数排序的解决方案
概述
外部排序是一种用于处理大型数据集合的排序算法,由于内存限制,无法将整个数据集放入内存中进行排序。因此,外部排序使用磁盘或其他外部存储介质来存储和处理数据
问题
当需要对大规模数据进行排序时,传统的排序算法(如快速排序、归并排序等)往往无法直接应用,因为它们要求所有数据同时存在于内存中。这种情况下,需要采用外部排序算法来解决这一问题。
思路
外部排序通常涉及以下几个步骤:
划分阶段: 数据被分割成适当大小的块,每个块可以容纳在内存中进行排序。
排序阶段: 每个块在内存中进行排序,常用的排序算法如快速排序、堆排序等。
归并阶段: 将所有的块归并到一个文件中。
具体解决方案
- 切分大文件至多个小文件
- 对每个小文件进行单独排序
- 维护一个优先队列,优先队列的大小为文件数目。
- 将每个小文件的第一个数字和该数字的来源放入优先队列中。
- 将优先队列中的堆顶弹出,堆顶数字即当前最小值,再从数字来源中读入一个新的数字进入优先队列。
- 关闭文件,删除临时文件
代码实现
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <queue>
#include <random>
#include <sstream>
#include <string>
#include <vector>
void generateRandomNumbers(const std::string &filename, int count)
{
std::ofstream file(filename);
if (!file.is_open()) {
std::cerr << "Unable to open file: " << filename << std::endl;
return;
}
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<int> distribution(1, count);
for (int i = 0; i < count; ++i) {
file << distribution(generator) << std::endl;
}
file.close();
}
void splitFile(const std::string &inputFile, const std::string &outputFilePrefix, int chunkSize, int &fileIndex)
{
std::ifstream input(inputFile);
std::vector<int> numbers;
numbers.reserve(chunkSize);
std::string line;
while (std::getline(input, line)) {
std::istringstream iss(line);
std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::back_inserter(numbers));
if (numbers.size() >= chunkSize) {
std::sort(numbers.begin(), numbers.end());
std::ofstream output(outputFilePrefix + std::to_string(fileIndex++) + ".tmp");
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(output, " "));
output << "\n";
numbers.clear();
}
}
if (!numbers.empty()) {
std::sort(numbers.begin(), numbers.end());
std::ofstream output(outputFilePrefix + std::to_string(fileIndex++) + ".tmp");
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(output, " "));
output << "\n";
}
}
void mergeFiles(const std::string &outputFile, const std::string &inputFilePrefix, int fileCount)
{
std::vector<std::ifstream> inputs(fileCount);
std::vector<int> numbers(fileCount);
std::vector<bool> activeInputs(fileCount, true);
for (int i = 0; i < fileCount; i++) {
inputs[i].open(inputFilePrefix + std::to_string(i) + ".tmp");
inputs[i] >> numbers[i];
}
std::ofstream output(outputFile);
struct Compare {
bool operator()(const std::pair<int, int> &a, const std::pair<int, int> &b)
{
return a.first > b.first;
}
};
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, Compare> pq;
for (int i = 0; i < fileCount; i++) {
if (activeInputs[i]) {
pq.emplace(numbers[i], i);
if (!(inputs[i] >> numbers[i])) {
activeInputs[i] = false;
}
}
}
while (!pq.empty()) {
int minValue = pq.top().first;
int minIndex = pq.top().second;
pq.pop();
output << minValue << "\n";
if (activeInputs[minIndex]) {
pq.emplace(numbers[minIndex], minIndex);
if (!(inputs[minIndex] >> numbers[minIndex])) {
activeInputs[minIndex] = false;
}
}
}
for (int i = 0; i < fileCount; i++) {
inputs[i].close();
}
}
void deleteTempFiles(const std::string &inputFilePrefix, int fileCount)
{
for (int i = 0; i < fileCount; i++) {
std::string fileName = inputFilePrefix + std::to_string(i) + ".tmp";
std::remove(fileName.c_str());
}
}
int main()
{
const std::string filename = "numbers.txt";
const int count = 100000000;
const std::string inputFile = "numbers.txt";
const std::string outputFile = "sorted_numbers.txt";
const std::string tempFilePrefix = "temp_";
const int chunkSize = 10000;
int fileIndex = 0;
generateRandomNumbers(filename, count);
splitFile(inputFile, tempFilePrefix, chunkSize, fileIndex);
mergeFiles(outputFile, tempFilePrefix, fileIndex);
deleteTempFiles(tempFilePrefix, fileIndex);
return 0;
}