C++ 七大排序算法详解:选择、冒泡、插入、计数、堆、快速与归并排序

1. 引言

排序算法是计算机科学中最基础也最重要的算法之一。无论是数据库索引、搜索引擎结果排序,还是日常开发中的数据处理,排序都无处不在。掌握排序算法的原理与实现,不仅能帮助我们写出更高效的代码,更是理解算法设计与复杂度分析的最佳入门途径。

本文将以 C++ 代码为例,详细讲解七大经典排序算法:选择排序、冒泡排序、插入排序、计数排序、堆排序、快速排序和归并排序。我们将从算法思想、代码实现、复杂度分析三个维度逐一展开,并在最后给出横向对比与选型建议。

2. 选择排序(Selection Sort)

2.1 算法思想

选择排序的核心思想非常直观:每一轮从未排序区间中选出最小(或最大)的元素,放到已排序区间的末尾。经过 n-1 轮选择后,整个数组就有序了。

它的特点是交换次数少,每轮最多交换一次,但比较次数固定为 n(n-1)/2,与初始数据顺序无关。

2.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void selectionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        int minIdx = i;
        for (int j = i + 1; j < n; ++j) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        if (minIdx != i) {
            swap(arr[i], arr[minIdx]);
        }
    }
}

int main() {
    vector<int> arr = {64, 25, 12, 22, 11};
    selectionSort(arr);
    for (int x : arr) cout << x << " ";
    return 0;
}

2.3 复杂度分析

指标
最好时间复杂度 O(n²)
最坏时间复杂度 O(n²)
平均时间复杂度 O(n²)
空间复杂度 O(1)
稳定性 不稳定

3. 冒泡排序(Bubble Sort)

3.1 算法思想

冒泡排序通过相邻元素的比较和交换,让较大的元素像气泡一样逐渐"浮"到数组末尾。每一轮结束后,当前未排序区间的最大值就位。通过设置标志位,可以在数组已经有序时提前退出,这是它相对选择排序的一个优势。

3.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void bubbleSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        bool swapped = false;
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) break;  // 提前退出
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    bubbleSort(arr);
    for (int x : arr) cout << x << " ";
    return 0;
}

3.3 复杂度分析

指标
最好时间复杂度 O(n)
最坏时间复杂度 O(n²)
平均时间复杂度 O(n²)
空间复杂度 O(1)
稳定性 稳定

4. 插入排序(Insertion Sort)

4.1 算法思想

插入排序的思想类似整理扑克牌:将数组分为已排序和未排序两部分,每次从未排序部分取出第一个元素,在已排序部分从后向前扫描,找到合适位置插入。对于近乎有序的数据,插入排序的效率非常高。

4.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void insertionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 1; i < n; ++i) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            --j;
        }
        arr[j + 1] = key;
    }
}

int main() {
    vector<int> arr = {12, 11, 13, 5, 6};
    insertionSort(arr);
    for (int x : arr) cout << x << " ";
    return 0;
}

4.3 复杂度分析

指标
最好时间复杂度 O(n)
最坏时间复杂度 O(n²)
平均时间复杂度 O(n²)
空间复杂度 O(1)
稳定性 稳定

5. 计数排序(Counting Sort)

5.1 算法思想

计数排序是一种非比较型排序算法,适用于数据范围较小且已知的整数序列。它通过统计每个元素出现的次数,再根据计数信息将元素放回正确位置。时间复杂度可以达到 O(n+k),其中 k 是数据范围。

5.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void countingSort(vector<int>& arr) {
    if (arr.empty()) return;
    int maxVal = *max_element(arr.begin(), arr.end());
    int minVal = *min_element(arr.begin(), arr.end());
    int range = maxVal - minVal + 1;

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

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

int main() {
    vector<int> arr = {4, 2, 2, 8, 3, 3, 1};
    countingSort(arr);
    for (int x : arr) cout << x << " ";
    return 0;
}

5.3 复杂度分析

指标
最好时间复杂度 O(n+k)
最坏时间复杂度 O(n+k)
平均时间复杂度 O(n+k)
空间复杂度 O(k)
稳定性 稳定

6. 堆排序(Heap Sort)

6.1 算法思想

堆排序利用二叉堆(通常用数组实现)的性质进行排序。首先将数组构建成一个大顶堆,此时堆顶元素就是最大值;将堆顶与末尾元素交换,然后对剩余元素重新调整堆,重复此过程即可完成排序。堆排序的时间复杂度稳定为 O(n log n)。

6.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void heapify(vector<int>& arr, int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;

    if (left < n && arr[left] > arr[largest]) largest = left;
    if (right < n && arr[right] > arr[largest]) largest = right;

    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(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) {
        swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}

int main() {
    vector<int> arr = {12, 11, 13, 5, 6, 7};
    heapSort(arr);
    for (int x : arr) cout << x << " ";
    return 0;
}

6.3 复杂度分析

指标
最好时间复杂度 O(n log n)
最坏时间复杂度 O(n log n)
平均时间复杂度 O(n log n)
空间复杂度 O(1)
稳定性 不稳定

7. 快速排序(Quick Sort)

7.1 算法思想

快速排序采用分治策略:选择一个基准元素(pivot),将数组划分为小于基准和大于基准的两部分,然后递归地对两部分进行排序。快速排序的平均性能非常优秀,是实际应用最广泛的排序算法之一。为了避免最坏情况,通常会采用随机选择基准或三数取中法。

7.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; ++j) {
        if (arr[j] < pivot) {
            ++i;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int main() {
    vector<int> arr = {10, 7, 8, 9, 1, 5};
    quickSort(arr, 0, arr.size() - 1);
    for (int x : arr) cout << x << " ";
    return 0;
}

7.3 复杂度分析

指标
最好时间复杂度 O(n log n)
最坏时间复杂度 O(n²)
平均时间复杂度 O(n log n)
空间复杂度 O(log n)
稳定性 不稳定

8. 归并排序(Merge Sort)

8.1 算法思想

归并排序同样采用分治策略:将数组不断二分,直到每个子数组只有一个元素,然后两两合并,在合并过程中完成排序。归并排序是稳定的排序算法,时间复杂度稳定为 O(n log n),但需要额外的 O(n) 空间。

8.2 C++ 代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void merge(vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    vector<int> L(n1), R(n2);
    for (int i = 0; i < n1; ++i) L[i] = arr[left + i];
    for (int j = 0; j < n2; ++j) R[j] = arr[mid + 1 + j];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k++] = L[i++];
        } else {
            arr[k++] = R[j++];
        }
    }
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

void mergeSort(vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}

int main() {
    vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    mergeSort(arr, 0, arr.size() - 1);
    for (int x : arr) cout << x << " ";
    return 0;
}

8.3 复杂度分析

指标
最好时间复杂度 O(n log n)
最坏时间复杂度 O(n log n)
平均时间复杂度 O(n log n)
空间复杂度 O(n)
稳定性 稳定

9. 七大排序算法对比与选型建议

9.1 综合对比

算法 最好时间复杂度 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定
冒泡排序 O(n) O(n²) O(n²) O(1) 稳定
插入排序 O(n) O(n²) O(n²) O(1) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(k) 稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定
快速排序 O(n log n) O(n log n) O(n²) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定

9.2 选型建议

面对不同场景,应结合数据规模、有序程度、取值范围和稳定性要求来选择合适的排序算法。

当数据量较小时,插入排序凭借简单的实现和较低常数开销往往表现最佳;

若数据近乎有序,插入排序可达到接近 O(n) 的线性效率,是理想选择。

当数据范围有限且为整数时,计数排序能以 O(n+k) 的线性时间完成排序,远优于比较型算法。

若对稳定性有严格要求,归并排序是稳妥之选,它能在 O(n log n) 时间内保持相等元素的相对顺序,代价是需要 O(n) 的额外空间。

对于通用的大规模数据排序,快速排序平均性能最优,是实际应用最广泛的选择;

若需兼顾最坏情况下的性能保证且空间受限,堆排序的 O(n log n) 稳定上界更具优势。

10. 实战应用场景与选型建议

在实际项目中,排序算法的选择往往不是追求理论上的最优复杂度,而是结合数据规模、数据特征和稳定性要求综合权衡。下面针对每种排序算法给出典型应用场景,并说明选型思路。

10.1 各排序算法的典型应用场景

选择排序:适用于数据量很小且对稳定性没有要求的场景,例如对几十个元素进行简单排序;也常用于教学演示,帮助理解最基础的排序思想。由于比较次数固定,它不适合大规模数据。

冒泡排序:适合数据量小且近乎有序的场景,借助提前退出标志可以在有序时达到 O(n) 的线性效率;也常用于算法入门教学。在实际工程中,冒泡排序更多作为理解相邻交换思想的示例,较少直接用于生产环境。

插入排序:非常适合近乎有序的小数据集,例如在线插入维护有序列表、处理实时到达的少量数据;同时它也是快速排序等高级算法在小规模子数组上的常用优化手段,能显著降低常数开销。

计数排序:适用于数据范围有限且为整数的场景,例如对学生成绩(0-100 分)排序、统计年龄分布、对固定范围内的 ID 或状态码排序。当 k 远小于 n 时,计数排序能以 O(n+k) 的线性时间完成排序,远优于比较型算法。

堆排序:适合需要稳定 O(n log n) 最坏时间复杂度的场景,且对额外空间敏感,例如嵌入式系统或内存受限环境中的排序;也常用于实现优先队列、Top-K 问题(如从海量数据中找出最大的 K 个元素)等场景。

快速排序:是通用大规模数据排序的首选,广泛应用于标准库和数据库索引构建中,例如 C++ 的 sort 函数、文件系统排序等。配合随机基准或三数取中法,快速排序在绝大多数场景下都能发挥出色的平均性能。

归并排序:适合对稳定性有严格要求且内存充足的场景,例如对链表排序、外部排序(处理无法一次性载入内存的大文件)、多路归并等。它能在 O(n log n) 时间内保持相等元素的相对顺序,代价是需要 O(n) 的额外空间。

10.2 实际项目中的选型思路

在实际项目中,可以按照以下思路快速确定排序算法:

首先判断数据规模,若数据量很小(如几十个元素),优先选择插入排序,其实现简单且常数开销低;若数据量较大,则进入下一步。

其次分析数据特征,若数据近乎有序,插入排序可达到接近 O(n) 的效率;若数据是范围有限的整数,计数排序能以线性时间完成排序。

再次考虑稳定性要求,若业务需要保持相等元素的相对顺序(如按多个字段排序时),应优先选择归并排序或插入排序等稳定算法。

最后权衡空间约束,若内存受限且需要最坏情况下的性能保证,堆排序是稳妥之选;若内存充足且追求平均性能,快速排序通常是最佳选择。

相关推荐
一只旭宝1 小时前
Python 与 C/C++ 内存模型对比总结
c语言·c++·python
心态还需努力呀2 小时前
把 Flameshot 编译进鸿蒙 PC:一次 Qt C++ 截图工具的源码级移植实战
c++·qt·harmonyos
shehuiyuelaiyuehao2 小时前
算法44,模拟算法,数青蛙
算法·哈希算法·散列表
hanlin032 小时前
刷题笔记:力扣第287题-寻找重复数
笔记·算法·leetcode
kevin_kang2 小时前
第02章 对话时间线与延迟分析
算法
科学实验家2 小时前
最小生成树:Prim,kruskal
数据结构·c++·算法
学习智者2 小时前
《玄》IDE v3.6.3重磅发布:全功能修复与性能飞跃
开发语言·c++·ide·算法·中文语言 玄
Jasmine_llq2 小时前
《P10263 [GESP202403 八级] 公倍数问题》
算法·数论·快速 io 优化·埃氏筛(倍数枚举)·线性遍历求和
2402_882893862 小时前
C++异常:从概念到实践的一篇博客
c++·异常