方案设计
- 分块读取与排序:
- 将超大型数据集按一定大小(例如每个块包含10000个浮点数,这个大小可根据内存限制调整)分块读入内存。假设内存一次能容纳
M
个浮点数,每个块的大小为B
(B <= M
)。
- 对每个块使用
std::sort
进行排序。排序后的块可临时存储在文件中。
- 多路归并:
- 使用优先队列(
std::priority_queue
)来实现多路归并。优先队列的比较函数按照浮点数从小到大排序。
- 从每个已排序的块文件中读取第一个数,放入优先队列中。
- 每次从优先队列中取出最小的数,写入结果文件。然后从对应的块文件中再读取下一个数放入优先队列,直到所有块文件的数据都处理完。
代码示例(简化的C++ 代码框架)
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <fstream>
// 定义块的大小,根据内存限制调整
const int BLOCK_SIZE = 10000;
// 定义优先队列中元素的结构体
struct Compare {
bool operator()(const std::pair<float, int>& a, const std::pair<float, int>& b) {
return a.first > b.first;
}
};
void sortLargeFile(const std::string& inputFileName, const std::string& outputFileName) {
std::ifstream inputFile(inputFileName, std::ios::binary);
std::vector<float> block(BLOCK_SIZE);
int blockIndex = 0;
while (inputFile.read(reinterpret_cast<char*>(block.data()), sizeof(float) * BLOCK_SIZE)) {
std::sort(block.begin(), block.end());
std::string tempFileName = "temp_" + std::to_string(blockIndex) + ".bin";
std::ofstream tempFile(tempFileName, std::ios::binary);
tempFile.write(reinterpret_cast<const char*>(block.data()), sizeof(float) * BLOCK_SIZE);
tempFile.close();
blockIndex++;
}
// 处理剩余不足BLOCK_SIZE的数据
int remaining = inputFile.gcount() / sizeof(float);
if (remaining > 0) {
block.resize(remaining);
inputFile.seekg(-static_cast<std::streamoff>(remaining * sizeof(float)), std::ios::cur);
inputFile.read(reinterpret_cast<char*>(block.data()), sizeof(float) * remaining);
std::sort(block.begin(), block.end());
std::string tempFileName = "temp_" + std::to_string(blockIndex) + ".bin";
std::ofstream tempFile(tempFileName, std::ios::binary);
tempFile.write(reinterpret_cast<const char*>(block.data()), sizeof(float) * remaining);
tempFile.close();
blockIndex++;
}
inputFile.close();
std::priority_queue<std::pair<float, int>, std::vector<std::pair<float, int>>, Compare> pq;
std::vector<std::ifstream> tempFiles(blockIndex);
std::vector<int> fileIndices(blockIndex, 0);
for (int i = 0; i < blockIndex; ++i) {
tempFiles[i].open("temp_" + std::to_string(i) + ".bin", std::ios::binary);
float num;
tempFiles[i].read(reinterpret_cast<char*>(&num), sizeof(float));
pq.push({num, i});
}
std::ofstream outputFile(outputFileName, std::ios::binary);
while (!pq.empty()) {
auto top = pq.top();
pq.pop();
outputFile.write(reinterpret_cast<const char*>(&top.first), sizeof(float));
int fileIndex = top.second;
if (fileIndices[fileIndex] < BLOCK_SIZE && tempFiles[fileIndex].read(reinterpret_cast<char*>(&top.first), sizeof(float))) {
fileIndices[fileIndex]++;
pq.push({top.first, fileIndex});
}
}
for (auto& file : tempFiles) {
file.close();
std::remove(("temp_" + std::to_string(&file - &tempFiles[0]) + ".bin").c_str());
}
outputFile.close();
}
时间复杂度分析
- 分块排序:假设数据集大小为
N
,块大小为B
,则块数k = ceil(N / B)
。对每个块排序的时间复杂度为O(B log B)
,因此分块排序的总时间复杂度为O(k * B log B) = O(N log B)
(因为k * B = N
)。
- 多路归并:优先队列每次操作(插入和删除)的时间复杂度为
O(log k)
。总共需要处理N
个数,因此多路归并的时间复杂度为O(N log k)
。
- 综合起来,整个算法的时间复杂度为
O(N log B + N log k) = O(N (log B + log k)) = O(N log(N))
(因为k = ceil(N / B)
,log B + log k = log(B * k) = log N
)。
空间复杂度分析
- 分块排序:在分块排序过程中,最大空间占用是一个块的大小
B
,空间复杂度为O(B)
。
- 多路归并:优先队列中最多存储
k
个元素,每个元素大小为O(1)
,因此优先队列的空间复杂度为O(k)
。同时,还需要k
个文件指针,空间复杂度也为O(k)
。
- 综合起来,空间复杂度为
O(B + k)
。由于k = ceil(N / B)
,在内存利用合理的情况下,B
和k
都相对N
较小,空间复杂度远小于O(N)
。具体来说,如果B
取一个合适的值(接近内存限制),空间复杂度主要取决于优先队列,为O(ceil(N / B))
。