7. 堆排序(Heap Sort)

结论速描:把数组看成完全二叉堆,建堆后反复取堆顶(最大/最小)放到末尾并重新调整堆。时间稳定 O(n log n),空间 O(1),但不稳定且常数较大。

直观想法

使用二叉堆(通常在数组中原地实现)。首先把数组构造成最大堆(父节点 >= 子节点),然后把堆顶(最大)与末尾交换,缩小堆范围并对堆顶做下沉维护堆性质,重复直到堆空。最后数组从尾到头是有序的。

步骤举例

[3,6,5,1,2,4]

  • 建堆(heapify)后可能 [6,3,5,1,2,4]
  • 交换堆顶 6 与末尾 4 → [4,3,5,1,2,6],对前 n-1 个元素重新 heapify。
  • 重复直到排序完成。

不变式与正确性

维护堆性质(每次 heapify 后根是当前未取出的最大值),取出后保证剩余元素仍能形成堆。

复杂度来源

建堆 O(n)(线性时间的 Floyd build-heap),每次取堆顶并 heapify O(log n),进行 n 次 → O(n log n)。

优化/变种

  • Smoothsort(Dijkstra)在近乎有序情况下更好,但实现复杂。
  • 使用索引堆或外部堆(对外排序)也常见。

稳定性/内存/并行

  • 不稳定(交换破坏相等元素顺序)。
  • 原地 O(1) 额外空间(相比归并更省内存)。
  • 并行化较不方便,但可以对 heapify 做局部并行改进。

—## 复杂度

情况 时间复杂度 空间 稳定性
所有 O(n log n) O(1) ❌ 不稳定

实现

void heapify(std::vector<int>& arr, int n, int i) {
    int largest = i, l = 2*i + 1, r = 2*i + 2;
    if (l < n && arr[l] > arr[largest]) largest = l;
    if (r < n && arr[r] > arr[largest]) largest = r;
    if (largest != i) {
        std::swap(arr[i], arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = n/2 - 1; i >= 0; --i) heapify(arr, n, i);
    for (int i = n - 1; i > 0; --i) {
        std::swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}

8. 计数排序(Counting Sort)

结论速描:对整数或可映射为小范围整数的元素计数,然后通过前缀和恢复排序。线性时间 O(n+k),但空间依赖于值域 k。

直观想法

假设元素值在 [min, max] 范围内且范围小。先统计每个值出现的次数(计数数组 count),再把 count 累加得到前缀和(每个值应放的位置上界),从原数组从后向前放置元素到输出数组(这样可以得到稳定排序)。

步骤举例

[2,5,3,2] 值域 2…5:

  • count[2]=2,count[3]=1,count[5]=1
  • 前缀和 transform -> positions: count cumulative
  • 从后向前放置保证稳定

不变式与正确性

前缀和给出每个值的最终位置分段,从后向前放可以保持原序(稳定)。

复杂度来源

时间 O(n + k):统计与前缀和都是线性。空间 O(k + n)。

限制/变种

  • 只适合值域 k 不是很大的情况(否则不实际)。
  • 处理负数:平移值(减 min)。
  • 值是浮点/复杂类型则需映射或变换。

稳定性/内存/并行

  • 可稳定实现。
  • 并行化容易(计数可并行聚合)。

复杂度

情况 时间复杂度 空间 稳定性
所有 O(n + k) O(k) ✅ 稳定

实现

void countingSort(std::vector<int>& arr) {
    int maxVal = *max_element(arr.begin(), arr.end());
    int minVal = *min_element(arr.begin(), arr.end());
    int range = maxVal - minVal + 1;
    std::vector<int> count(range, 0), output(arr.size());

    for (int x : arr) count[x - minVal]++;
    for (int i = 1; i < range; ++i) count[i] += count[i - 1];
    for (int i = arr.size() - 1; i >= 0; --i)
        output[--count[arr[i] - minVal]] = arr[i];
    arr = output;
}

9. 桶排序(Bucket Sort)

结论速描:将元素按范围/哈希分配到若干桶,对每个桶内部排序(常用插入排序),然后合并。对均匀分布或可分段分布的数据非常高效。

直观想法

把数据域划分为 k 个区间(桶),把元素放到对应桶中。若元素均匀分布,期望每个桶内元素很少,桶内排序成本低(常为 O(1) 平均)。最后按桶顺序把每个桶的有序元素依次输出。

步骤举例

[0.12,0.45,0.23,0.78]

  • 桶0:[0…0.25) → [0.12,0.23],桶1:[0.25…0.5) → [0.45] 等
  • 每桶排序后合并 → 总有序

不变式与正确性

桶划分保证不同桶之间的值大小段互不重叠,合并保有序。

复杂度来源

若均匀分配且每桶排序平均开销低,则期望 O(n + k)。最坏若分配不均则退化为 O(n²)(若所有元素落在一个桶)。

变体/优化

  • 选择合适的桶数 k(通常与 n 成正比)。
  • 桶内使用适当排序算法(插入 / 归并)。
  • 对有序或近似有序数据桶内开销更小。

稳定性/内存/并行

  • 桶内部排序可用稳定算法以保持全局稳定。
  • 需要 O(n + k) 额外空间。
  • 桶可并行化(天然并行)。

—## 复杂度

情况 时间复杂度 空间 稳定性
平均 O(n + k) O(n + k) ✅ 稳定

实现

void bucketSort(std::vector<float>& arr) {
    int n = arr.size();
    std::vector<std::vector<float>> buckets(n);
    for (float x : arr) {
        int idx = n * x; // 假设x ∈ [0,1)
        buckets[idx].push_back(x);
    }
    for (auto& b : buckets)
        std::sort(b.begin(), b.end());
    int k = 0;
    for (auto& b : buckets)
        for (float x : b)
            arr[k++] = x;
}

10、 基数排序(Radix Sort)

结论速描:把整数按每一位(或每若干位)从低位到高位(LSD)或高位到低位(MSD)进行若干次稳定计数排序,最终得到全局排序。对定长整数或字符串非常高效,时间 O(d·(n+k)),d 为位数。

直观想法

对十进制整数,从最低位开始做一次稳定计数排序,使得按低位已排序;再按次低位排序,直到最高位:多次稳定基于某个位的排序后,整体按所有位组合排序。MSD 版本先按高位划分再递归(常用于字符串排序)。

步骤举例(LSD)

数组 [170,45,75,90,802,24,2,66]

  • 按个位做稳定计数 → 中间结果
  • 按十位做稳定计数 → 中间结果
  • 按百位做稳定计数 → 最终排序

不变式与正确性

每一次稳定按某位排序都保持此前按低位的相对顺序;最终按所有位组合排序。

复杂度来源

每位做一次计数排序 O(n+k)。总共 d 位 → O(d(n+k))。对固定位数(例如 32 位整数)d 常数,接近 O(n)。

限制与变体

  • LSD(低位优先)适合整数;MSD 更适合字符串或变长键。
  • 处理负数需映射或分离正负。
  • 基数基数(radix)大小选择影响常数:用 2^8 位(字节)基数通常实现不错。

稳定性/内存/并行

  • 可稳定实现(每次计数排序稳定)。
  • 额外空间 O(n + k)。
  • 极易并行化(每次计数和分配可并行)。

复杂度

情况 时间复杂度 空间 稳定性
所有 O(d·(n + k)) O(n + k) ✅ 稳定

实现

void countingSortRadix(std::vector<int>& arr, int exp) {
    int n = arr.size();
    std::vector<int> output(n), count(10, 0);
    for (int x : arr) count[(x / exp) % 10]++;
    for (int i = 1; i < 10; ++i) count[i] += count[i - 1];
    for (int i = n - 1; i >= 0; --i) {
        int idx = (arr[i] / exp) % 10;
        output[--count[idx]] = arr[i];
    }
    arr = output;
}

void radixSort(std::vector<int>& arr) {
    int maxVal = *max_element(arr.begin(), arr.end());
    for (int exp = 1; maxVal / exp > 0; exp *= 10)
        countingSortRadix(arr, exp);
}

总结对比表

算法 平均时间 最坏时间 空间 稳定 适用场景
冒泡 O(n²) O(n²) O(1) 教学、简单
选择 O(n²) O(n²) O(1) 内存有限
插入 O(n²) O(n²) O(1) 小规模
希尔 O(n log n) O(n²) O(1) 中规模
归并 O(n log n) O(n log n) O(n) 稳定需求
快排 O(n log n) O(n²) O(log n) 通用首选
堆排 O(n log n) O(n log n) O(1) 不稳定容忍
计数 O(n+k) O(n+k) O(k) 整数范围小
桶排 O(n+k) O(n²) O(n+k) 均匀分布
基数 O(d·n) O(d·n) O(n+k) 整数、多位数

综合比较与实践建议

  • 学习/教学:冒泡、选择、插入(理解排序基本思想)。
  • 小数组 / 部分有序:插入排序、希尔排序(阈值切换)。
  • 通用库排序:快速排序变体(Introsort) → 实用且高效(std::sort)。
  • 稳定且大规模:归并排序或 TimSort(Python/Java 的字符串排序使用的混合稳定排序,实战很好)。
  • 内存敏感场景:堆排序(原地、O(1) 额外空间)。
  • 整数/固定位数/范围受限:计数、基数、桶排序(线性时间)。
  • 并行/分布式:归并、基数、桶有天然并行性;快速排序也可并行化左右子问题。

实现要点建议

  • 对于实战 std::sort(C++)已经做了高质量实现(Introsort),推荐直接使用。若需要稳定排序,使用 std::stable_sort(通常基于归并)。

  • 对于排序函数,注意:

    • 选择合适的 pivot(随机化或 median-of-three)。
    • 对小区间切换到插入排序(减少递归与常数)。
    • 避免不必要的复制,优先写原地算法并注意迭代器类别。
    • 使用合适的数据结构(数组/向量更适合 SIMD / 并行优化)。
  • 在性能关键路径上,考虑:内存访问局部性、分支预测影响(分支消除、分段处理)、SIMD 批量比较与压缩存储(适合基数/归并的实现)。


综合示例-所有算法实现和测试示例

src/utils.hpp

(生成随机数组 / 验证正确性 / 打印耗时)

#pragma once
#include <vector>
#include <random>
#include <chrono>
#include <iostream>
#include <algorithm>

namespace utils {

inline std::vector<int> generateRandomVector(size_t n, int min = 0, int max = 100000) {
    static std::mt19937 rng(std::random_device{}());
    std::uniform_int_distribution<int> dist(min, max);
    std::vector<int> v(n);
    for (auto& x : v) x = dist(rng);
    return v;
}

inline bool isSorted(const std::vector<int>& v) {
    return std::is_sorted(v.begin(), v.end());
}

template<typename Func>
inline void benchmark(const std::string& name, Func f, std::vector<int> v) {
    auto start = std::chrono::high_resolution_clock::now();
    f(v);
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> ms = end - start;
    std::cout << name << " : " << ms.count() << " ms, "
              << (isSorted(v) ? " sorted" : " error") << std::endl;
}

} // namespace utils

各算法实现


quick_sort.hpp

含:

  • 三数取中 pivot 策略
  • 小数组切换插入排序
  • 尾递归优化
#pragma once
#include <vector>
#include <algorithm>

namespace sorting {

namespace detail {

inline int medianOfThree(std::vector<int>& v, int lo, int hi) {
    int mid = lo + (hi - lo) / 2;
    if (v[hi] < v[lo]) std::swap(v[hi], v[lo]);
    if (v[mid] < v[lo]) std::swap(v[mid], v[lo]);
    if (v[hi] < v[mid]) std::swap(v[hi], v[mid]);
    std::swap(v[mid], v[hi - 1]); // pivot 放到 hi-1
    return v[hi - 1];
}

inline void insertionSort(std::vector<int>& v, int lo, int hi) {
    for (int i = lo + 1; i <= hi; ++i) {
        int key = v[i];
        int j = i - 1;
        while (j >= lo && v[j] > key) {
            v[j + 1] = v[j];
            --j;
        }
        v[j + 1] = key;
    }
}

inline int partition(std::vector<int>& v, int lo, int hi, int pivot) {
    int i = lo, j = hi - 1;
    for (;;) {
        while (v[++i] < pivot) {}
        while (v[--j] > pivot) {}
        if (i < j) std::swap(v[i], v[j]);
        else break;
    }
    std::swap(v[i], v[hi - 1]);
    return i;
}

inline void quickSortImpl(std::vector<int>& v, int lo, int hi) {
    const int INSERTION_THRESHOLD = 16;
    while (lo < hi) {
        if (hi - lo + 1 <= INSERTION_THRESHOLD) {
            insertionSort(v, lo, hi);
            return;
        }

        int pivot = medianOfThree(v, lo, hi);
        int p = partition(v, lo, hi, pivot);

        // 尾递归优化:优先递归较小子区间
        if (p - lo < hi - p) {
            quickSortImpl(v, lo, p - 1);
            lo = p + 1;
        } else {
            quickSortImpl(v, p + 1, hi);
            hi = p - 1;
        }
    }
}

} // namespace detail

inline void quickSort(std::vector<int>& v) {
    if (v.size() <= 1) return;
    detail::quickSortImpl(v, 0, (int)v.size() - 1);
}

} // namespace sorting

merge_sort.hpp

#pragma once
#include <vector>
#include <algorithm>

namespace sorting {

inline void merge(std::vector<int>& v, std::vector<int>& tmp, int l, int m, int r) {
    int i = l, j = m + 1, k = l;
    while (i <= m && j <= r) {
        if (v[i] <= v[j]) tmp[k++] = v[i++];
        else tmp[k++] = v[j++];
    }
    while (i <= m) tmp[k++] = v[i++];
    while (j <= r) tmp[k++] = v[j++];
    for (int t = l; t <= r; ++t) v[t] = tmp[t];
}

inline void mergeSortImpl(std::vector<int>& v, std::vector<int>& tmp, int l, int r) {
    if (l >= r) return;
    int m = (l + r) / 2;
    mergeSortImpl(v, tmp, l, m);
    mergeSortImpl(v, tmp, m + 1, r);
    if (v[m] <= v[m + 1]) return; // 优化:已有序跳过
    merge(v, tmp, l, m, r);
}

inline void mergeSort(std::vector<int>& v) {
    if (v.size() <= 1) return;
    std::vector<int> tmp(v.size());
    mergeSortImpl(v, tmp, 0, (int)v.size() - 1);
}

} // namespace sorting

heap_sort.hpp

#pragma once
#include <vector>
#include <algorithm>

namespace sorting {

inline void heapify(std::vector<int>& v, int n, int i) {
    int largest = i, l = 2 * i + 1, r = 2 * i + 2;
    if (l < n && v[l] > v[largest]) largest = l;
    if (r < n && v[r] > v[largest]) largest = r;
    if (largest != i) {
        std::swap(v[i], v[largest]);
        heapify(v, n, largest);
    }
}

inline void heapSort(std::vector<int>& v) {
    int n = (int)v.size();
    for (int i = n / 2 - 1; i >= 0; --i) heapify(v, n, i);
    for (int i = n - 1; i > 0; --i) {
        std::swap(v[0], v[i]);
        heapify(v, i, 0);
    }
}

} // namespace sorting

insertion_sort.hpp

#pragma once
#include <vector>

namespace sorting {
inline void insertionSort(std::vector<int>& v) {
    for (size_t i = 1; i < v.size(); ++i) {
        int key = v[i];
        int j = i - 1;
        while (j >= 0 && v[j] > key) {
            v[j + 1] = v[j];
            if (j == 0) { --j; break; }
            --j;
        }
        v[j + 1] = key;
    }
}
} // namespace sorting

src/main.cpp

#include "utils.hpp"
#include "sorting/quick_sort.hpp"
#include "sorting/merge_sort.hpp"
#include "sorting/heap_sort.hpp"
#include "sorting/insertion_sort.hpp"

int main() {
    constexpr size_t N = 100000;
    auto data = utils::generateRandomVector(N);

    utils::benchmark("QuickSort", [](auto v){ sorting::quickSort(v); }, data);
    utils::benchmark("MergeSort", [](auto v){ sorting::mergeSort(v); }, data);
    utils::benchmark("HeapSort",  [](auto v){ sorting::heapSort(v); }, data);
    utils::benchmark("InsertionSort", [](auto v){ sorting::insertionSort(v); }, data);

    return 0;
}


sort_algorithms.hpp

#pragma once
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>
#include <iterator>

// =========================================================
// Counting Sort (计数排序)
// 适用场景:非负整数、值域范围不大(max-min 不大)
// =========================================================
inline void countingSort(std::vector<int>& arr) {
    if (arr.empty()) return;

    auto [minIt, maxIt] = std::minmax_element(arr.begin(), arr.end());
    int minVal = *minIt, maxVal = *maxIt;
    int range = maxVal - minVal + 1;

    // 如果范围太大,不适合计数排序
    if (range > 1e7) {
        std::sort(arr.begin(), arr.end());
        return;
    }

    std::vector<int> count(range, 0);
    for (int val : arr) count[val - minVal]++;

    int idx = 0;
    for (int i = 0; i < range; ++i)
        while (count[i]-- > 0)
            arr[idx++] = i + minVal;
}

// =========================================================
// Radix Sort (基数排序) - LSD 低位优先
// 适用场景:非负整数、浮点可通过 reinterpret_cast 转换
// =========================================================
inline void radixSort(std::vector<int>& arr) {
    if (arr.empty()) return;
    int maxVal = *std::max_element(arr.begin(), arr.end());
    int exp = 1;
    std::vector<int> buffer(arr.size());

    while (maxVal / exp > 0) {
        std::vector<int> count(10, 0);
        for (int val : arr)
            count[(val / exp) % 10]++;
        for (int i = 1; i < 10; ++i)
            count[i] += count[i - 1];

        // 稳定排序,从后往前
        for (int i = arr.size() - 1; i >= 0; --i)
            buffer[--count[(arr[i] / exp) % 10]] = arr[i];

        arr.swap(buffer);
        exp *= 10;
    }
}

// =========================================================
// Bucket Sort (桶排序)
// 适用场景:数据均匀分布在 [0,1) 或已归一化
// =========================================================
inline void bucketSort(std::vector<float>& arr) {
    if (arr.empty()) return;

    int n = arr.size();
    std::vector<std::vector<float>> buckets(n);

    // 分桶
    for (float val : arr) {
        int idx = std::min(n - 1, static_cast<int>(val * n));
        buckets[idx].push_back(val);
    }

    // 每个桶内部排序
    for (auto& b : buckets)
        std::sort(b.begin(), b.end());

    // 合并
    int idx = 0;
    for (auto& b : buckets)
        for (float v : b)
            arr[idx++] = v;
}

// =========================================================
// Bubble Sort (冒泡排序)
// 适用场景:小规模数据;可检测有序提前退出
// =========================================================
template <typename T>
inline void bubbleSort(std::vector<T>& arr) {
    size_t n = arr.size();
    bool swapped = true;
    for (size_t i = 0; i < n - 1 && swapped; ++i) {
        swapped = false;
        for (size_t j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
    }
}

// =========================================================
// Selection Sort (选择排序)
// 思想:每次选择最小元素放到当前前端
// =========================================================
template <typename T>
inline void selectionSort(std::vector<T>& arr) {
    size_t n = arr.size();
    for (size_t i = 0; i < n - 1; ++i) {
        size_t minIdx = i;
        for (size_t j = i + 1; j < n; ++j)
            if (arr[j] < arr[minIdx]) minIdx = j;
        if (minIdx != i)
            std::swap(arr[i], arr[minIdx]);
    }
}

// =========================================================
// Shell Sort (希尔排序)
// 思想:分组插排,逐步缩小 gap
// 优化:使用 Knuth 序列 gap = gap*3 + 1
// =========================================================
template <typename T>
inline void shellSort(std::vector<T>& arr) {
    size_t n = arr.size();
    size_t gap = 1;
    while (gap < n / 3) gap = gap * 3 + 1;

    while (gap >= 1) {
        for (size_t i = gap; i < n; ++i) {
            T temp = arr[i];
            size_t j = i;
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = temp;
        }
        gap /= 3;
    }
}

main.cpp

#include "sort_algorithms.hpp"
#include <iostream>
#include <random>
#include <chrono>

template<typename F, typename T>
void test_sort(F func, std::vector<T> arr, const std::string& name) {
    auto start = std::chrono::high_resolution_clock::now();
    func(arr);
    auto end = std::chrono::high_resolution_clock::now();

    bool sorted = std::is_sorted(arr.begin(), arr.end());
    auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

    std::cout << name << ": "
              << (sorted ? "OK" : "FAIL")
              << " | time = " << dur << " us\n";
}

int main() {
    constexpr int N = 10000;
    std::mt19937 gen(42);
    std::uniform_int_distribution<int> dist(0, 10000);
    std::uniform_real_distribution<float> distf(0.0f, 1.0f);

    std::vector<int> data;
    data.reserve(N);
    for (int i = 0; i < N; ++i) data.push_back(dist(gen));

    test_sort([](auto& v){ countingSort(v); }, data, "Counting Sort");
    test_sort([](auto& v){ radixSort(v); }, data, "Radix Sort");
    test_sort([](auto& v){ selectionSort(v); }, data, "Selection Sort");
    test_sort([](auto& v){ bubbleSort(v); }, data, "Bubble Sort");
    test_sort([](auto& v){ shellSort(v); }, data, "Shell Sort");

    // 桶排序专用浮点数
    std::vector<float> fdata;
    fdata.reserve(N);
    for (int i = 0; i < N; ++i) fdata.push_back(distf(gen));
    test_sort([](auto& v){ bucketSort(v); }, fdata, "Bucket Sort");

    return 0;
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐