外部排序


外部排序(External Sorting)

1. 什么是外部排序?

当数据太大,无法一次性加载到内存时,需要使用外部存储(如磁盘)进行排序,称为外部排序

2. 外部排序的核心思想

  • 分块排序(分段排序):先将大文件分成多个可以放入内存的小块,每个小块单独排序后存入外存。
  • 归并排序(合并排序):再将这些已排序的小块进行多路归并,最终得到一个完整的有序文件。

常见外部排序方法:

  1. 多路归并排序(Multi-way Merge Sort)(最常用)
  2. 置换-选择排序(Replacement Selection)

多路归并排序(Multi-way Merge Sort)

原理

  • 第一步:分块排序
    • 把大数据文件切成多个小块,每个小块能放入内存
    • 在内存中对每个小块进行排序(可用快速排序归并排序)。
    • 排序后的小块写回外存(磁盘)。
  • 第二步:多路归并
    • 采用k路归并方法,将多个已排序的小块合并为一个大文件。
    • 维护一个最小堆(堆排序)来找出当前所有块中的最小元素,逐步写入最终的输出文件。

代码实现

由于无法直接操作超大数据,这里用多个小文件模拟外部数据,并使用多路归并排序进行合并。

#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

const int CHUNK_SIZE = 5; // 内存能容纳的最大元素数,模拟有限内存

// 生成大文件
void generateLargeFile(const string& filename, int numElements) {
    ofstream outFile(filename);
    if (!outFile) {
        cerr << "无法创建文件!" << endl;
        return;
    }

    srand(time(0));
    for (int i = 0; i < numElements; i++) {
        outFile << rand() % 100 << endl; // 生成0~99之间的随机数
    }
    outFile.close();
}

// 读取大文件并分块排序
vector<string> splitAndSortChunks(const string& filename) {
    ifstream inFile(filename);
    vector<string> chunkFiles;
    vector<int> buffer;

    int chunkIndex = 0;
    while (true) {
        buffer.clear();
        int num;
        for (int i = 0; i < CHUNK_SIZE && inFile >> num; i++) {
            buffer.push_back(num);
        }

        if (buffer.empty()) break;

        // 对块进行排序
        sort(buffer.begin(), buffer.end());

        // 将排序后的数据写入小文件
        string chunkFilename = "chunk_" + to_string(chunkIndex++) + ".txt";
        chunkFiles.push_back(chunkFilename);
        ofstream outFile(chunkFilename);
        for (int num : buffer) {
            outFile << num << endl;
        }
        outFile.close();
    }

    inFile.close();
    return chunkFiles;
}

// 多路归并排序(最小堆)
void mergeSortedChunks(const vector<string>& chunkFiles, const string& outputFilename) {
    ofstream outFile(outputFilename);
    if (!outFile) {
        cerr << "无法创建输出文件!" << endl;
        return;
    }

    // 优先队列(最小堆),存放 {值, 文件索引}
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
    vector<ifstream> inFiles(chunkFiles.size());

    // 打开所有小文件,并读取第一个数放入堆
    for (int i = 0; i < chunkFiles.size(); i++) {
        inFiles[i].open(chunkFiles[i]);
        int num;
        if (inFiles[i] >> num) {
            minHeap.push({num, i});
        }
    }

    // 归并排序
    while (!minHeap.empty()) {
        auto [value, fileIndex] = minHeap.top();
        minHeap.pop();

        outFile << value << endl; // 写入最终结果

        // 从同一文件读取下一个数
        int nextNum;
        if (inFiles[fileIndex] >> nextNum) {
            minHeap.push({nextNum, fileIndex});
        }
    }

    // 关闭所有文件
    for (auto& file : inFiles) file.close();
    outFile.close();
}

// **主函数**
int main() {
    string inputFile = "large_input.txt";
    string outputFile = "sorted_output.txt";
    
    // **生成大文件**
    generateLargeFile(inputFile, 20);

    // **第一步:分块排序**
    vector<string> chunkFiles = splitAndSortChunks(inputFile);

    // **第二步:多路归并**
    mergeSortedChunks(chunkFiles, outputFile);

    cout << "外部排序完成!已写入文件:" << outputFile << endl;
    return 0;
}

代码解析

  1. generateLargeFile
    • 生成一个包含 20 个随机数 的大文件(模拟超大数据)。
  2. splitAndSortChunks
    • 读取固定大小(CHUNK_SIZE)的数据块。
    • 逐块排序后存入小文件(模拟内存受限)。
  3. mergeSortedChunks
    • 维护一个最小堆,用于多路归并。
    • 每次从最小堆中取最小值,然后从该文件读取下一个值。
    • 直到所有文件归并完成。

时间复杂度分析

  • 分块排序:O(n log k)(k = 每块大小)
  • 归并排序:O(n log m)(m = 块的数量)

整体时间复杂度:O(n log n),与归并排序相同


总结

外部排序步骤 方式
分块排序 将大文件分割成多个小块,每个块在内存中排序后存储到外存
归并排序 使用最小堆进行多路归并,合并多个有序块

应用场景

大数据排序(大于内存大小的文件)
数据库索引构建(MySQL、MongoDB)
搜索引擎数据处理(Google、Bing)

外部排序是处理超大数据集的核心技术,适用于无法一次性加载入内存的情况。


文章作者: Gustavo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 Gustavo !
评论
  目录