MST

星途 面试题库

面试题:C++ STL sort性能优化在特定场景下的极致探索

假设你正在处理一个超大型的数据集(例如数亿个浮点数),使用STL的`sort`算法进行排序。由于内存限制,不能一次性将所有数据读入内存。请设计一个完整的方案,结合`sort`算法的特性以及可能的辅助数据结构和技术,在保证排序正确性的前提下,最大程度优化性能,并详细分析时间复杂度和空间复杂度。
42.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

方案设计

  1. 分块读取与排序
    • 将超大型数据集按一定大小(例如每个块包含10000个浮点数,这个大小可根据内存限制调整)分块读入内存。假设内存一次能容纳M个浮点数,每个块的大小为BB <= M)。
    • 对每个块使用std::sort进行排序。排序后的块可临时存储在文件中。
  2. 多路归并
    • 使用优先队列(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();
}

时间复杂度分析

  1. 分块排序:假设数据集大小为N,块大小为B,则块数k = ceil(N / B)。对每个块排序的时间复杂度为O(B log B),因此分块排序的总时间复杂度为O(k * B log B) = O(N log B)(因为k * B = N)。
  2. 多路归并:优先队列每次操作(插入和删除)的时间复杂度为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)。

空间复杂度分析

  1. 分块排序:在分块排序过程中,最大空间占用是一个块的大小B,空间复杂度为O(B)
  2. 多路归并:优先队列中最多存储k个元素,每个元素大小为O(1),因此优先队列的空间复杂度为O(k)。同时,还需要k个文件指针,空间复杂度也为O(k)
    • 综合起来,空间复杂度为O(B + k)。由于k = ceil(N / B),在内存利用合理的情况下,Bk都相对N较小,空间复杂度远小于O(N)。具体来说,如果B取一个合适的值(接近内存限制),空间复杂度主要取决于优先队列,为O(ceil(N / B))