1 冒泡排序(Bubble Sort)
一、核心概念
最简单的基础交换排序,重复遍历数组,相邻两个元素两两比较,逆序则交换;每一轮遍历会把当前未排序区间最大值 "冒泡" 到区间末尾。
二、算法思路
-
外层循环控制冒泡轮次,n 个元素最多 n-1 轮;
-
内层循环遍历未排序区间,相邻元素对比交换;
-
优化标记:本轮无交换代表数组已有序,直接提前终止。
三、核心特性
稳定排序、原地排序(无需额外数组)
四、代码示例
#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,将数组划分成两部分:左区间全部≤基准、右区间全部≥基准,再递归处理左右子区间。
二、算法思路
-
选基准(首元素 / 末尾 / 随机 / 三数取中);
-
双指针遍历,把小于基准放左侧,大于放右侧;
-
基准归位,递归左、右子数组。
三、核心特性
不稳定排序、原地排序,综合性能最优,工业最常用排序
四、代码示例(基础左右指针版)
#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,最后一次完整插入排序。
二、算法思路
-
初始化增量 gap = n/2;
-
每组相隔 gap 的元素做插入排序;
-
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;
-
开辟临时数组,合并两个有序区间;
-
合并结果回填原数组,逐层向上合并。
三、核心特性
稳定排序;非原地排序,需要额外辅助空间;最坏平均最好均为(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,下沉修复堆;
-
下沉调整:父节点与左右子节点对比,和最大子节点交换,递归下沉。
三、核心特性
不稳定排序、原地排序;时间复杂度全程稳定 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))
六、优缺点
优点:原地排序,复杂度稳定无退化;不依赖有序原始数组;
缺点:不稳定排序;缓存不友好,数据跳跃访问;常数开销比快排大。
七、易错拓展
-
大顶堆升序,小顶堆降序;
-
堆排序不适合近乎有序的小数组;
-
TopK 问题优先使用堆排序,无需完整排序。
6 插入排序(Insert Sort)
一、核心概念
将数组分为有序区、无序区,依次取出无序区第一个元素,向前插入到有序区合适位置,维持有序区始终有序。
二、算法思路
-
默认第一个元素为初始有序区;
-
依次遍历无序区元素,缓存当前待插入值;
-
有序区从后向前遍历,大于缓存值则后移;
-
空位放入缓存值,扩大有序区。
三、核心特性
稳定排序、原地排序;近乎有序数组效率极高
四、代码示例
#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;
-
循环至全部元素进入有序区。
三、核心特性
不稳定排序、原地排序;交换次数远少于冒泡
四、代码示例
#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)
一、核心概念
非比较型整数排序算法,不通过元素两两比较排序,而是通过统计数组中每个数值出现的次数,利用数值映射关系,直接将元素放回有序位置。核心思想是统计频次、前缀求和、反向回填,仅适用于已知数值范围的整数数据。
二、算法思路
-
遍历原数组,找到最大值与最小值,确定计数数组区间范围,压缩空间开销;
-
开辟计数数组,遍历原数组,统计每个数值的出现次数;
-
对计数数组做前缀和处理,确定每个数值在有序数组中的最后下标位置;
-
倒序遍历原数组,根据计数数组的下标信息,反向回填元素至结果数组,保证排序稳定性;
-
将有序结果数组覆盖原数组,完成排序。
三、核心特性
稳定排序、非原地排序、非比较排序;排序效率不受数据顺序影响,仅和数据量、数值范围相关
四、代码示例
#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值剧增,空间开销爆炸;无法排序浮点数、字符串等非整数数据。
七、易错拓展
-
朴素版计数排序(正向遍历)不稳定,前缀和+倒序回填是稳定排序的核心关键;
-
通过最值偏移(num-minVal)压缩计数数组长度,避免负数、大范围空值浪费空间;
-
适用场景:数据量大、数值范围小的整数排序(如年龄、分数、排名统计);
-
是基数排序的基础算法,多级基数排序依赖单次计数排序实现。