一、排序算法分类
1. 比较类排序
通过元素之间的比较来决定顺序,理论下界是 Ω(n log n)。
- 交换类:冒泡排序、快速排序
- 选择类:简单选择排序、堆排序
- 插入类:直接插入排序、希尔排序
- 归并类:归并排序
2. 非比较类排序
不直接比较元素大小,而是利用数据本身的特性(范围、位数等),可以突破 O(n log n) 的限制,达到线性时间。
- 计数排序
- 桶排序
- 基数排序
二、各大排序算法详解与对比
1. 冒泡排序(Bubble Sort)
思想:反复比较相邻元素,把较大的元素逐步"冒泡"到后面。
- 时间复杂度:最好 O(n),平均/最坏 O(n²)
- 空间复杂度:O(1)
- 稳定性:稳定
- 特点:实现简单,可优化提前结束,但效率低
适用:数据量很小,或几乎已经有序。
2. 选择排序(Selection Sort)
思想:每一轮从剩余元素中选出最小值,放到已排序部分的末尾。
- 时间复杂度:始终 O(n²)
- 空间复杂度:O(1)
- 稳定性:不稳定
- 特点:交换次数少(最多 n-1 次)
适用:交换成本较高的场景。
3. 插入排序(Insertion Sort)
思想:把数组分成已排序和未排序两部分,每次把未排序的第一个元素插入到已排序部分的正确位置。
- 时间复杂度:最好 O(n),平均/最坏 O(n²)
- 空间复杂度:O(1)
- 稳定性:稳定
- 特点:对近乎有序的数组效率极高
适用:小规模数据,或作为快速排序的优化(小数组转插入排序)。
4. 希尔排序(Shell Sort)
思想:插入排序的改进版。通过设定增量序列进行分组插入,逐步缩小增量。
- 时间复杂度:约 O(n^1.3)(取决于增量序列)
- 空间复杂度:O(1)
- 稳定性:不稳定
- 特点:比直接插入排序快很多,实现简单
适用:中等规模数据。
5. 堆排序(Heap Sort)
思想:利用堆结构,先建大顶堆,反复把堆顶最大值与末尾交换,再调整堆。
- 时间复杂度:始终 O(n log n)
- 空间复杂度:O(1)
- 稳定性:不稳定
- 特点:原地排序,时间稳定
适用:大数据量且内存受限的场景。
6. 归并排序(Merge Sort)
思想:分治。把数组分成两半,分别排序,再合并两个有序数组。
- 时间复杂度:始终 O(n log n)
- 空间复杂度:O(n)
- 稳定性:稳定
- 特点:时间稳定,适合链表排序
适用:需要稳定排序,或数据量大且可以接受额外空间。
7. 快速排序(Quick Sort)
思想:分治 + 分区。选基准值,把小于基准的放左边,大于基准的放右边,再递归。
- 时间复杂度:平均 O(n log n),最坏 O(n²)
- 空间复杂度:O(log n)
- 稳定性:不稳定
- 特点:实际运行通常最快,工程首选
优化手段:
- 随机基准 / 三数取中
- 小数组转插入排序
- 尾递归优化
适用:绝大多数通用场景。
8. 计数排序(Counting Sort)
思想:统计每个元素出现的次数,然后按顺序放回。
- 时间复杂度:O(n + k)(k 为数据范围)
- 空间复杂度:O(k)
- 稳定性:稳定
- 特点:非比较排序,速度极快
适用:数据范围不大的整数排序。
9. 桶排序(Bucket Sort)
思想:把数据分到有限数量的桶里,每个桶内部再排序,最后合并。
- 时间复杂度:平均 O(n + k),最坏 O(n²)
- 空间复杂度:O(n + k)
- 稳定性:稳定(取决于桶内排序)
- 特点:对数据分布均匀时效果好
适用:数据分布比较均匀的场景。
10. 基数排序(Radix Sort)
思想:按照个位、十位、百位等从低位到高位依次进行稳定排序(通常用计数排序)。
- 时间复杂度:O(d(n + k))(d 为位数)
- 空间复杂度:O(n + k)
- 稳定性:稳定
- 特点:非比较排序,适合位数有限的整数
适用:整数、字符串(定长)、手机号、学号等。
三、终极对比表
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定 | 原地 | 主要特点 |
|---|---|---|---|---|---|---|
| 冒泡 | O(n²) | O(n²) | O(1) | 是 | 是 | 简单,可提前结束 |
| 选择 | O(n²) | O(n²) | O(1) | 否 | 是 | 交换次数少 |
| 插入 | O(n²) | O(n²) | O(1) | 是 | 是 | 近乎有序时很快 |
| 希尔 | O(n^1.3) | O(n²) | O(1) | 否 | 是 | 插入排序的改进 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 否 | 是 | 时间稳定,原地 |
| 归并 | O(n log n) | O(n log n) | O(n) | 是 | 否 | 稳定,适合链表 |
| 快速 | O(n log n) | O(n²) | O(log n) | 否 | 是 | 实际最快,通用首选 |
| 计数 | O(n+k) | O(n+k) | O(k) | 是 | 否 | 范围小时极快 |
| 桶排序 | O(n+k) | O(n²) | O(n+k) | 是 | 否 | 数据分布均匀时高效 |
| 基数 | O(d(n+k)) | O(d(n+k)) | O(n+k) | 是 | 否 | 位数有限的整数 |
四、实际选型建议(非常重要)
- 通用情况 →
std::sort(底层通常是快速排序 + 堆排序 + 插入排序的混合,Introsort) - 需要稳定排序 → 归并排序 或 基数排序
- 数据量很小 → 插入排序
- 数据近乎有序 → 插入排序
- 内存极度受限 → 堆排序
- 数据是整数且范围小 → 计数排序
- 数据是整数且位数不多 → 基数排序
- 数据分布均匀 → 桶排序
五、十大排序算法完整可运行 C++ 代码合集
下面给出十大排序算法的完整 C++ 实现,统一风格整理,可直接复制到一个文件里编译运行。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
// ==================== 工具函数 ====================
void printArray(const vector<int>& arr, const string& name = "") {
if (!name.empty()) cout << name << ":";
for (int x : arr) cout << x << " ";
cout << endl;
}
// ==================== 1. 冒泡排序 ====================
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 - 1 - i; ++j) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) break; // 优化:提前结束
}
}
// ==================== 2. 选择排序 ====================
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]);
}
}
// ==================== 3. 插入排序 ====================
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;
}
}
// ==================== 4. 希尔排序 ====================
void ShellSort(vector<int>& arr) {
int n = arr.size();
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; ++i) {
int temp = arr[i];
int j = i;
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
// ==================== 5. 堆排序 ====================
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);
}
}
// ==================== 6. 归并排序 ====================
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> temp(right - left + 1);
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) temp[k++] = arr[i++];
else temp[k++] = arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
for (int p = 0; p < temp.size(); ++p) arr[left + p] = temp[p];
}
void MergeSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
// ==================== 7. 快速排序 ====================
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);
}
}
// ==================== 8. 计数排序 ====================
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);
vector<int> 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] - 1] = arr[i];
count[arr[i] - minVal]--;
}
arr = output;
}
// ==================== 9. 桶排序 ====================
void BucketSort(vector<int>& arr) {
if (arr.empty()) return;
int maxVal = *max_element(arr.begin(), arr.end());
int minVal = *min_element(arr.begin(), arr.end());
int bucketCount = max(1, (maxVal - minVal) / 10 + 1);
vector<vector<int>> buckets(bucketCount);
for (int x : arr) {
int idx = (x - minVal) / 10;
buckets[idx].push_back(x);
}
arr.clear();
for (auto& bucket : buckets) {
sort(bucket.begin(), bucket.end());
for (int x : bucket) arr.push_back(x);
}
}
// ==================== 10. 基数排序 ====================
void countingSortByDigit(vector<int>& arr, int exp) {
vector<int> output(arr.size());
vector<int> 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 = arr.size() - 1; i >= 0; --i) {
int digit = (arr[i] / exp) % 10;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
arr = output;
}
void RadixSort(vector<int>& arr) {
if (arr.empty()) return;
int maxVal = *max_element(arr.begin(), arr.end());
for (int exp = 1; maxVal / exp > 0; exp *= 10) {
countingSortByDigit(arr, exp);
}
}
// ==================== 主函数测试 ====================
int main() {
vector<int> data = {64, 34, 25, 12, 22, 11, 90, 5};
vector<int> a1 = data; BubbleSort(a1); printArray(a1, "冒泡排序");
vector<int> a2 = data; SelectionSort(a2); printArray(a2, "选择排序");
vector<int> a3 = data; InsertionSort(a3); printArray(a3, "插入排序");
vector<int> a4 = data; ShellSort(a4); printArray(a4, "希尔排序");
vector<int> a5 = data; HeapSort(a5); printArray(a5, "堆排序");
vector<int> a6 = data; MergeSort(a6, 0, a6.size() - 1); printArray(a6, "归并排序");
vector<int> a7 = data; QuickSort(a7, 0, a7.size() - 1); printArray(a7, "快速排序");
vector<int> a8 = data; CountingSort(a8); printArray(a8, "计数排序");
vector<int> a9 = data; BucketSort(a9); printArray(a9, "桶排序");
vector<int> a10 = data; RadixSort(a10); printArray(a10, "基数排序");
return 0;
}
六、总结
比较类排序的理论下界是 O(n log n),快速排序、归并排序、堆排序是其中的代表。非比较类排序可以突破这个限制,但需要数据满足特定条件。
实际工程中,快速排序(及其优化版本)是最常用的选择,稳定排序优先考虑归并。把这些算法的思想、复杂度、稳定性和适用场景真正理解后,排序这部分基本就通透了。