MST
星途 面试题库

面试题:C++ 并行化冒泡排序

使用C++ 中的多线程库(如OpenMP或C++11线程库)对冒泡排序进行并行化处理,以提高排序效率。描述并行化的策略,处理好线程同步等问题,并给出完整可运行的代码。同时分析并行化后的时间复杂度与空间复杂度的变化。
30.9万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

并行化策略

  1. OpenMP
    • OpenMP通过#pragma omp parallel for指令来并行化循环。对于冒泡排序,冒泡排序有两层循环,外层循环控制比较轮数,内层循环进行相邻元素的比较和交换。我们可以并行化内层循环,因为每一轮内层循环的比较操作相互独立。
    • 但是需要注意线程同步问题,由于多个线程可能同时访问和修改数组元素,需要使用互斥锁(mutex)来保证同一时间只有一个线程可以修改数组元素。
  2. C++11线程库
    • C++11线程库通过std::thread来创建线程。可以根据CPU核心数创建相应数量的线程,每个线程负责处理数组的一部分数据。
    • 对于冒泡排序,同样可以并行化内层循环。在线程间共享数据时,使用std::mutex来保护共享数据,避免数据竞争。同时,使用条件变量(std::condition_variable)来进行线程同步,确保所有线程完成一轮比较后再进入下一轮。

OpenMP实现代码

#include <iostream>
#include <omp.h>
#include <vector>

void bubbleSortParallelOpenMP(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        #pragma omp parallel for
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
            }
        }
    }
}

C++11线程库实现代码

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>

std::mutex mtx;
std::condition_variable cv;
std::atomic<int> completedThreads(0);
std::atomic<bool> allDone(false);

void bubbleSortPart(std::vector<int>& arr, int start, int end, int n) {
    while (!allDone) {
        for (int i = 0; i < n - 1; ++i) {
            for (int j = start; j < end && j < n - i - 1; ++j) {
                std::unique_lock<std::mutex> lock(mtx);
                if (arr[j] > arr[j + 1]) {
                    std::swap(arr[j], arr[j + 1]);
                }
                lock.unlock();
            }
        }
        completedThreads++;
        if (completedThreads == std::thread::hardware_concurrency()) {
            completedThreads = 0;
            cv.notify_all();
        } else {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, [] { return allDone || completedThreads == std::thread::hardware_concurrency(); });
        }
    }
}

void bubbleSortParallelC11(std::vector<int>& arr) {
    int n = arr.size();
    int numThreads = std::thread::hardware_concurrency();
    std::vector<std::thread> threads;
    int step = n / numThreads;
    for (int i = 0; i < numThreads; ++i) {
        int start = i * step;
        int end = (i == numThreads - 1)? n : (i + 1) * step;
        threads.emplace_back(bubbleSortPart, std::ref(arr), start, end, n);
    }
    std::this_thread::sleep_for(std::chrono::seconds(1));
    allDone = true;
    cv.notify_all();
    for (auto& th : threads) {
        th.join();
    }
}

时间复杂度分析

  1. 串行冒泡排序:时间复杂度为 (O(n^2)),其中 (n) 是数组的长度。因为有两层嵌套循环,外层循环执行 (n - 1) 次,内层循环执行 (n - i - 1) 次。
  2. 并行化后
    • OpenMP:理论上,并行化内层循环后,时间复杂度可以接近 (O(n \log n))。因为内层循环的比较操作并行执行,减少了整体的执行时间。但由于线程同步和调度等开销,实际效果可能达不到理想的 (O(n \log n)),仍然接近 (O(n^2))。
    • C++11线程库:同样,理想情况下时间复杂度可以接近 (O(n \log n)),但线程创建、同步等开销会影响实际性能,整体时间复杂度仍然近似 (O(n^2))。

空间复杂度分析

  1. 串行冒泡排序:空间复杂度为 (O(1)),因为只需要有限的额外空间用于交换元素。
  2. 并行化后
    • OpenMP:空间复杂度仍然为 (O(1)),OpenMP主要通过指令并行化循环,不额外增加大量空间。
    • C++11线程库:空间复杂度变为 (O(n)),因为需要创建多个线程,每个线程可能需要一定的栈空间,并且还需要一些额外的同步变量(如mutex、condition_variable等),总体空间开销增加。同时如果按照代码中线程划分数据的方式,虽然数据本身没有额外空间开销,但线程函数局部变量等会增加一定空间复杂度。