C++ 数据结构 八大基础排序算法专题

1 冒泡排序(Bubble Sort)

一、核心概念

最简单的基础交换排序,重复遍历数组,相邻两个元素两两比较,逆序则交换;每一轮遍历会把当前未排序区间最大值 "冒泡" 到区间末尾。

二、算法思路

  1. 外层循环控制冒泡轮次,n 个元素最多 n-1 轮;

  2. 内层循环遍历未排序区间,相邻元素对比交换;

  3. 优化标记:本轮无交换代表数组已有序,直接提前终止。

三、核心特性

稳定排序、原地排序(无需额外数组)

四、代码示例

复制代码
#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 swapFlag = false; // 有序标记
        for (int j = 0; j < n - 1 - i; ++j) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j], arr[j+1]);
                swapFlag = true;
            }
        }
        if (!swapFlag) break; // 无交换,提前退出
    }
}
​
int main() {
    vector<int> vec = {5,3,8,1,2};
    BubbleSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好(已有序):(O(n))

最坏 / 平均:(O(n^2))

空间复杂度:(O(1))

六、优缺点

优点:代码极简、稳定、原地排序、自带有序优化;

缺点:效率极低,大数据量完全不适用。

七、易错拓展

稳定排序:相等元素不会交换相对位置;适合小规模、近乎有序数组。

2 快速排序(Quick Sort)

一、核心概念

分治交换排序,选定基准值 pivot,将数组划分成两部分:左区间全部≤基准、右区间全部≥基准,再递归处理左右子区间。

二、算法思路

  1. 选基准(首元素 / 末尾 / 随机 / 三数取中);

  2. 双指针遍历,把小于基准放左侧,大于放右侧;

  3. 基准归位,递归左、右子数组。

三、核心特性

不稳定排序、原地排序,综合性能最优,工业最常用排序

四、代码示例(基础左右指针版)

复制代码
#include <iostream>
#include <vector>
using namespace std;
​
// 划分区间
int Partition(vector<int>& arr, int left, int right) {
    int pivot = arr[left];
    int l = left, r = right;
    while (l < r) {
        while (l < r && arr[r] >= pivot) r--;
        arr[l] = arr[r];
        while (l < r && arr[l] <= pivot) l++;
        arr[r] = arr[l];
    }
    arr[l] = pivot;
    return l;
}
​
// 快排递归
void QuickSort(vector<int>& arr, int l, int r) {
    if (l >= r) return;
    int mid = Partition(arr, l, r);
    QuickSort(arr, l, mid - 1);
    QuickSort(arr, mid + 1, r);
}
​
int main() {
    vector<int> vec = {5,3,8,1,2,9,4};
    QuickSort(vec, 0, vec.size()-1);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

平均 / 最好:(O(nlogn))

最坏(有序数组):(O(n^2))

空间复杂度:(O(logn))(递归栈)

六、优缺点

优点:平均速度极快,常数开销小,海量数据首选;

缺点:不稳定;极端有序数据会退化,递归深度过大栈溢出。

七、易错拓展

优化方案:随机基准、三数取中、小区间改用插入排序;标准库 sort 底层为改良快排。

3 希尔排序(Shell Sort)

一、核心概念

插入排序的优化版本,又称缩小增量排序;先按固定步长分组,组内插入排序,逐步缩小步长至 1,最后一次完整插入排序。

二、算法思路

  1. 初始化增量 gap = n/2;

  2. 每组相隔 gap 的元素做插入排序;

  3. gap = gap/2,重复分组排序,直到 gap=1。

三、核心特性

不稳定、原地排序;比普通插入排序快很多

四、代码示例

复制代码
#include <iostream>
#include <vector>
using namespace std;
​
void ShellSort(vector<int>& arr) {
    int n = arr.size();
    int gap = n / 2;
    for (; gap > 0; gap /= 2) {
        // 分组插入排序
        for (int i = gap; i < n; ++i) {
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j-gap] > temp; j -= gap) {
                arr[j] = arr[j-gap];
            }
            arr[j] = temp;
        }
    }
}
​
int main() {
    vector<int> vec = {9,7,5,3,1,2,4,6,8};
    ShellSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

平均:(O(n^{1.3}))

最坏:(O(n^2))

空间复杂度:(O(1))

六、优缺点

优点:中等数据量速度优于冒泡、普通插入;原地排序;

缺点:不稳定,增量序列无最优标准,大数据弱于快排 / 归并。

七、易错拓展

步长序列影响效率,常用:n/2、Knuth 序列;相等元素跨组交换会破坏稳定性。

4 归并排序(Merge Sort)

一、核心概念

分治稳定排序,先递归拆分数组至单个元素,再有序合并两个有序子数组,最终得到完整有序序列。

二、算法思路

  1. 二分拆分数组,拆到区间长度为 1;

  2. 开辟临时数组,合并两个有序区间;

  3. 合并结果回填原数组,逐层向上合并。

三、核心特性

稳定排序;非原地排序,需要额外辅助空间;最坏平均最好均为(O(nlogn))

四、代码示例

复制代码
#include <iostream>
#include <vector>
using namespace std;
​
// 合并两个有序区间
void Merge(vector<int>& arr, int l, int mid, int r, vector<int>& temp) {
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
        if (arr[i] <= arr[j]) temp[k++] = arr[i++];
        else temp[k++] = arr[j++];
    }
    while (i <= mid) temp[k++] = arr[i++];
    while (j <= r) temp[k++] = arr[j++];
    // 回填原数组
    for (int x = 0; x < k; ++x) arr[l + x] = temp[x];
}
​
// 归并递归拆分
void MergeSort(vector<int>& arr, int l, int r, vector<int>& temp) {
    if (l >= r) return;
    int mid = (l + r) / 2;
    MergeSort(arr, l, mid, temp);
    MergeSort(arr, mid+1, r, temp);
    Merge(arr, l, mid, r, temp);
}
​
int main() {
    vector<int> vec = {6,2,7,1,9,3};
    vector<int> temp(vec.size()); // 辅助数组
    MergeSort(vec, 0, vec.size()-1, temp);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好 / 平均 / 最坏:(O(nlogn))

空间复杂度:(O(n))(辅助数组)

六、优缺点

优点:稳定排序,时间复杂度稳定无退化;适合外排序(磁盘超大文件);

缺点:需要额外内存,原地归并实现复杂、效率下降。

七、易错拓展

适合要求稳定、海量外部数据场景;链表排序首选归并(链表无需额外数组)。

5 堆排序(Heap Sort)

一、核心概念

基于二叉堆的选择排序,利用大顶堆特性:父节点值大于左右子节点。

  1. 将无序数组构建为大顶堆;

  2. 堆顶是当前最大值,交换堆顶与堆末尾元素,最大值就位;

  3. 缩小堆范围,重新调整堆,循环直至全部有序。

二、算法思路

  1. 建堆:从最后一个非叶子节点向前遍历,逐层下沉调整;

  2. 堆排序主逻辑:堆顶与末尾交换,堆长度 - 1,下沉修复堆;

  3. 下沉调整:父节点与左右子节点对比,和最大子节点交换,递归下沉。

三、核心特性

不稳定排序、原地排序;时间复杂度全程稳定 O (nlogn)

四、代码示例

复制代码
#include <iostream>
#include <vector>
using namespace std;
// 下沉调整堆
void HeapAdjust(vector<int>& arr, int parent, int len) {
    int temp = arr[parent];
    // 左孩子下标
    int child = parent * 2 + 1;
    while (child < len) {
        // 取左右孩子中更大的
        if (child + 1 < len && arr[child] < arr[child + 1])
            child++;
        if (temp >= arr[child]) break;
        arr[parent] = arr[child];
        parent = child;
        child = parent * 2 + 1;
    }
    arr[parent] = temp;
}
​
// 堆排序入口
void HeapSort(vector<int>& arr) {
    int n = arr.size();
    // 1. 构建大顶堆
    for (int i = n / 2 - 1; i >= 0; i--)
        HeapAdjust(arr, i, n);
    // 2. 堆顶交换末尾,逐步排序
    for (int i = n - 1; i > 0; i--) {
        swap(arr[0], arr[i]);
        HeapAdjust(arr, 0, i);
    }
}
​
int main() {
    vector<int> vec = {3, 1, 9, 5, 7, 2};
    HeapSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好 / 平均 / 最坏:(O(nlogn))

空间复杂度:(O(1))

六、优缺点

优点:原地排序,复杂度稳定无退化;不依赖有序原始数组;

缺点:不稳定排序;缓存不友好,数据跳跃访问;常数开销比快排大。

七、易错拓展

  1. 大顶堆升序,小顶堆降序;

  2. 堆排序不适合近乎有序的小数组;

  3. TopK 问题优先使用堆排序,无需完整排序。

6 插入排序(Insert Sort)

一、核心概念

将数组分为有序区、无序区,依次取出无序区第一个元素,向前插入到有序区合适位置,维持有序区始终有序。

二、算法思路

  1. 默认第一个元素为初始有序区;

  2. 依次遍历无序区元素,缓存当前待插入值;

  3. 有序区从后向前遍历,大于缓存值则后移;

  4. 空位放入缓存值,扩大有序区。

三、核心特性

稳定排序、原地排序;近乎有序数组效率极高

四、代码示例

复制代码
#include <iostream>
#include <vector>
using namespace std;
void InsertSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 1; i < n; i++) {
        int temp = arr[i];
        int j;
        // 有序区后移
        for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
            arr[j + 1] = arr[j];
        }
        arr[j + 1] = temp;
    }
}
​
int main() {
    vector<int> vec = {4, 2, 5, 1, 3};
    InsertSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好(完全有序):(O(n))

平均 / 最坏(逆序):(O(n^2))

空间复杂度:(O(1))

六、优缺点

优点:稳定、原地、代码极简;数组越有序速度越快;

缺点:逆序大数据下效率极低。

七、易错拓展

希尔排序本质是分组插入排序;快排递归拆分后的小区间常用插入排序优化。

7 选择排序(Select Sort)

一、核心概念

分为有序区、无序区,每一轮遍历无序区找到最小值,与无序区首元素交换,最小值进入有序区末尾。

二、算法思路

  1. 有序区初始为空,无序区覆盖全部数组;

  2. 遍历无序区间记录最小值下标;

  3. 最小值和无序区首位交换,有序区长度 + 1;

  4. 循环至全部元素进入有序区。

三、核心特性

不稳定排序、原地排序;交换次数远少于冒泡

四、代码示例

复制代码
#include <iostream>
#include <vector>
using namespace std;
void SelectSort(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;
        }
        swap(arr[i], arr[minIdx]);
    }
}
​
int main() {
    vector<int> vec = {7, 3, 8, 2, 1};
    SelectSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好 / 平均 / 最坏:(O(n^2))

空间复杂度:(O(1))

六、优缺点

优点:交换操作极少,对比冒泡节省交换开销;原地排序;

缺点:无论数组是否有序,都要完整遍历,无提前退出优化;不稳定。

七、易错拓展

不稳定案例:[2, 2, 1],第一轮最小 1 交换第一位 2,两个 2 相对顺序颠倒;

仅适合极小数据量,工程几乎不使用。

8 计数排序(Counting Sort)

一、核心概念

非比较型整数排序算法,不通过元素两两比较排序,而是通过统计数组中每个数值出现的次数,利用数值映射关系,直接将元素放回有序位置。核心思想是统计频次、前缀求和、反向回填,仅适用于已知数值范围的整数数据

二、算法思路

  1. 遍历原数组,找到最大值与最小值,确定计数数组区间范围,压缩空间开销;

  2. 开辟计数数组,遍历原数组,统计每个数值的出现次数;

  3. 对计数数组做前缀和处理,确定每个数值在有序数组中的最后下标位置;

  4. 倒序遍历原数组,根据计数数组的下标信息,反向回填元素至结果数组,保证排序稳定性;

  5. 将有序结果数组覆盖原数组,完成排序。

三、核心特性

稳定排序、非原地排序、非比较排序;排序效率不受数据顺序影响,仅和数据量、数值范围相关

四、代码示例

复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
​
void CountingSort(vector<int>& arr) {
    if (arr.empty()) return;
​
    // 1. 找到数组最值,确定计数区间
    int maxVal = *max_element(arr.begin(), arr.end());
    int minVal = *min_element(arr.begin(), arr.end());
    int range = maxVal - minVal + 1;
​
    // 2. 统计每个数值出现次数
    vector<int> count(range, 0);
    for (int num : arr) {
        count[num - minVal]++;
    }
​
    // 3. 前缀和:计算每个数值的最后存储下标
    for (int i = 1; i < range; ++i) {
        count[i] += count[i - 1];
    }
​
    // 4. 倒序遍历,反向回填,保证稳定性
    vector<int> res(arr.size());
    for (int i = arr.size() - 1; i >= 0; --i) {
        int pos = count[arr[i] - minVal] - 1;
        res[pos] = arr[i];
        count[arr[i] - minVal]--;
    }
​
    // 5. 结果覆盖原数组
    arr.swap(res);
}
​
int main() {
    vector<int> vec = {5,3,8,1,2,2,3,5};
    CountingSort(vec);
    for (auto v : vec) cout << v << " ";
    return 0;
}

五、时空复杂度

最好 / 平均 / 最坏:O(n+k)(n为数据个数,k为数值取值范围)

空间复杂度:O(n+k)(需要额外结果数组、计数数组)

六、优缺点

优点:无比较操作,线性时间复杂度,效率极高;天然支持稳定排序;实现简单、常数开销极小;

缺点:局限性极强,仅支持整数排序;数值范围极大时,k值剧增,空间开销爆炸;无法排序浮点数、字符串等非整数数据。

七、易错拓展

  1. 朴素版计数排序(正向遍历)不稳定,前缀和+倒序回填是稳定排序的核心关键

  2. 通过最值偏移(num-minVal)压缩计数数组长度,避免负数、大范围空值浪费空间;

  3. 适用场景:数据量大、数值范围小的整数排序(如年龄、分数、排名统计);

  4. 是基数排序的基础算法,多级基数排序依赖单次计数排序实现。

相关推荐
Tim_101 小时前
【C++】017、new/delete与malloc/free的区别
java·数据结构·算法
YYYing.1 小时前
【C++大型项目之高性能服务器框架 (七) 】Socket与ByteArray模块
服务器·c++·后端·框架·高性能·c/c++
从零开始的代码生活_2 小时前
C++ list 原理与实践:双向链表、迭代器与简化实现
开发语言·c++·后端·学习·算法·链表·list
charlie1145141912 小时前
现代C++工程实践——WeakPtr 第二关:核心骨架与控制块
开发语言·c++
2401_869769593 小时前
内容9 string 1
c++
ttod_qzstudio3 小时前
【软考算法】软件设计师下午第四题之动态规划:0-1 背包与最长公共子序列的“填表艺术“
算法·动态规划·软考
柒和远方3 小时前
LeetCode 139. 单词拆分 —— 从暴力回溯到 DP 完全背包
javascript·python·算法
岑梓铭4 小时前
《考研408数据结构》第六章(6.3 图的遍历)复习笔记
数据结构·笔记·考研··拓扑排序·408
XH华4 小时前
C++语言第二章类和对象(下)
开发语言·c++