排序算法是计算机科学中最基础也最核心的算法之一,根据排序原理可分为比较类排序 (通过比较元素大小确定顺序)和非比较类排序(利用数值特性排序)。
一、比较类排序算法
比较类排序通过比较元素间的大小关系调整顺序,时间复杂度下限为O(nlogn)(基于决策树模型证明)。
1. 冒泡排序(Bubble Sort)
核心思想:重复遍历数组,每次比较相邻元素,逆序则交换,使最大元素逐步"浮"到数组末端。
步骤:
- 从数组头部开始,依次比较相邻元素,逆序则交换;
- 每轮遍历后,最大元素移至未排序区间末尾,缩小未排序范围;
- 若某轮无交换,说明数组有序,提前终止。
特性:
- 时间复杂度:最好O(n)(优化后),最坏/平均O(n²);
- 空间复杂度:O(1)(原地排序);
- 稳定性:稳定(相等元素不交换)。
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 - 1 - i; ++j) { // 未排序区间[0, n-1-i]
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 num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
2. 选择排序(Selection Sort)
核心思想:每轮从待排序区间找到最小元素,与区间首元素交换,逐步扩大已排序区间。
步骤:
- 初始未排序区间为整个数组;
- 遍历未排序区间,找到最小元素,与区间首元素交换;
- 已排序区间扩大1个元素,重复至排序完成。
特性:
- 时间复杂度:最好/最坏/平均均为O(n²)(必须遍历找最小值);
- 空间复杂度:O(1);
- 稳定性:不稳定(如
[2, 2, 1],交换后破坏相对顺序)。
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 minIndex = i; // 记录未排序区间最小值索引
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[minIndex]) minIndex = j;
}
swap(arr[i], arr[minIndex]); // 交换到已排序区间
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
selectionSort(arr);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
3. 插入排序(Insertion Sort)
核心思想:将数组分为"已排序"和"未排序"两部分,每次从无序部分取元素,插入到有序部分的正确位置(类似整理扑克牌)。
步骤:
- 初始已排序区间为第一个元素;
- 从第二个元素开始,与有序区间元素从后往前比较,找到插入位置;
- 移动元素腾出位置,插入当前元素,扩大有序区间。
特性:
- 时间复杂度:最好O(n)(已排序),最坏/平均O(n²);
- 空间复杂度:O(1);
- 稳定性:稳定(相等元素插入后方)。
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; // 已排序区间末尾
// 移动元素为key腾位置
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key; // 插入key
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
insertionSort(arr);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
4. 希尔排序(Shell Sort)
核心思想 :插入排序的改进版,通过"增量分组"减少元素移动次数。按增量gap分组,每组插入排序;逐步减小gap至1,最终整体有序。
步骤:
- 初始增量
gap = n/2,后续gap = gap/2; - 按
gap分组,每组插入排序; - 减小
gap重复,直至gap = 1(完整插入排序)。
特性:
- 时间复杂度:取决于增量,平均约O(n^1.3),最坏O(n²);
- 空间复杂度:O(1);
- 稳定性:不稳定(分组排序破坏相对顺序)。
C++实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
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 key = arr[i];
int j = i;
// 组内插入排序(步长gap)
while (j >= gap && arr[j - gap] > key) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = key;
}
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
shellSort(arr);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
5. 归并排序(Merge Sort)
核心思想:基于分治法,递归拆分数组为两个子数组,分别排序后合并为有序数组。
步骤:
- 拆分:数组从中间分为两部分,递归拆分至子数组长度为1;
- 合并:创建临时数组,比较两子数组元素,按序放入临时数组,再复制回原数组。
特性:
- 时间复杂度:最好/最坏/平均均为O(nlogn);
- 空间复杂度:O(n)(需临时数组);
- 稳定性:稳定(合并时保持相等元素顺序)。
C++实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
// 合并[left, mid]和[mid+1, right]两个有序子数组
void merge(vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1, n2 = right - mid;
vector<int> L(n1), R(n2); // 临时数组
for (int i = 0; i < n1; ++i) L[i] = arr[left + i];
for (int i = 0; i < n2; ++i) R[i] = arr[mid + 1 + i];
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 = {64, 34, 25, 12, 22, 11, 90};
mergeSort(arr, 0, arr.size() - 1);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
6. 快速排序(Quick Sort)
核心思想:分治法的典型应用,选择"基准元素"将数组分为两部分(左≤基准≤右),递归排序两部分。
步骤:
- 选择基准(如随机元素);
- 分区:小于基准放左,大于基准放右,基准位置固定;
- 递归排序左右两部分。
特性:
- 时间复杂度:平均O(nlogn),最坏O(n²)(可通过基准优化避免);
- 空间复杂度:O(logn)(递归栈);
- 稳定性:不稳定(分区交换破坏顺序)。
C++实现:
cpp
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
// 分区操作(随机基准)
int partition(vector<int>& arr, int left, int right) {
srand(time(0));
int pivotIndex = left + rand() % (right - left + 1); // 随机基准
swap(arr[left], arr[pivotIndex]);
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) j--; // 右指针找小于基准的元素
while (i < j && arr[i] <= pivot) i++; // 左指针找大于基准的元素
if (i < j) swap(arr[i], arr[j]);
}
swap(arr[left], arr[i]); // 基准放到最终位置
return i;
}
// 快速排序主函数
void quickSort(vector<int>& arr, int left, int right) {
if (left < right) {
int pivotPos = partition(arr, left, right);
quickSort(arr, left, pivotPos - 1); // 左子数组
quickSort(arr, pivotPos + 1, right); // 右子数组
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
quickSort(arr, 0, arr.size() - 1);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
7. 堆排序(Heap Sort)
核心思想:利用大顶堆(父节点≥子节点)特性,每次提取堆顶(最大值)放到数组末尾,再调整剩余元素为大顶堆,重复至排序完成。
步骤:
- 构建大顶堆:从最后一个非叶子节点开始向下调整;
- 排序:堆顶与末尾元素交换,缩小堆范围,调整新堆顶,重复至堆为空。
特性:
- 时间复杂度:最好/最坏/平均均为O(nlogn);
- 空间复杂度:O(1)(原地排序);
- 稳定性:不稳定(交换破坏顺序)。
C++实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
// 向下调整堆(保证大顶堆特性)
void heapify(vector<int>& arr, int n, int root) {
int largest = root;
int left = 2 * root + 1; // 左子节点
int right = 2 * root + 2; // 右子节点
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest != root) {
swap(arr[root], 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); // 调整剩余i个元素
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
heapSort(arr);
for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
return 0;
}
二、非比较类排序算法
非比较类排序不依赖元素比较,利用数值特性实现排序,时间复杂度可达O(n),但适用场景有限。
1. 计数排序(Counting Sort)
核心思想 :假设元素值在[min, max]范围内,统计每个值的出现次数,再根据次数重构有序数组。
步骤:
- 确定值范围
[min, max],创建计数数组; - 统计每个元素出现次数,计算前缀和(确定元素位置);
- 从后往前遍历原数组,按计数数组放置元素(保证稳定性)。
特性:
- 时间复杂度:O(n + k)(n为元素数,k为值范围);
- 空间复杂度:O(n + k);
- 稳定性:稳定。
适用场景 :元素为整数且值范围小(如k ≈ n)。
C++实现:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void countingSort(vector<int>& arr) {
if (arr.empty()) return;
int minVal = *min_element(arr.begin(), arr.end());
int maxVal = *max_element(arr.begin(), arr.end());
int range = maxVal - minVal + 1;
vector<int> count(range, 0), output(arr.size());
// 统计次数
for (int num : arr) count[num - 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;
}
int main() {
vector<int> arr = {4, 2, 2, 8, 3, 3, 1};
countingSort(arr);
for (int num : arr) cout << num << " "; // 1 2 2 3 3 4 8
return 0;
}
2. 桶排序(Bucket Sort)
核心思想:将元素分到若干"桶"中,桶内用其他排序(如插入排序),最后按桶顺序合并。
步骤:
- 确定桶数量和范围,将元素分配到对应桶;
- 桶内排序;
- 合并所有桶元素。
特性:
- 时间复杂度:O(n + nlog(n/m))(m为桶数);
- 空间复杂度:O(n + m);
- 稳定性:取决于桶内排序算法。
适用场景:元素分布均匀(如0-1之间的浮点数)。
C++实现:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bucketSort(vector<float>& arr) {
int n = arr.size();
if (n == 0) return;
vector<vector<float>> buckets(n); // 创建n个桶
// 分配元素到桶(假设元素在[0,1))
for (float num : arr) {
int idx = n * num;
buckets[idx].push_back(num);
}
// 桶内排序
for (auto& bucket : buckets) sort(bucket.begin(), bucket.end());
// 合并桶
int idx = 0;
for (auto& bucket : buckets) {
for (float num : bucket) arr[idx++] = num;
}
}
int main() {
vector<float> arr = {0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434};
bucketSort(arr);
for (float num : arr) cout << num << " "; // 0.1234 0.3434 0.565 0.656 0.665 0.897
return 0;
}
3. 基数排序(Radix Sort)
核心思想:按元素"位数"从低到高排序,每轮对当前位用计数排序(保证稳定性)。
步骤:
- 确定最大元素的位数
d; - 从最低位到最高位,每轮对当前位执行计数排序;
- 最终整体有序。
特性:
- 时间复杂度:O(d*(n + k))(d为位数,k为每一位范围);
- 空间复杂度:O(n + k);
- 稳定性:稳定。
适用场景:整数或字符串(位数固定)。
C++实现:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 获取x的第exp位数字(如exp=1取个位)
int getDigit(int x, int exp) {
return (x / exp) % 10;
}
// 对第exp位计数排序(基数排序的一轮)
void countSortRadix(vector<int>& arr, int exp) {
int n = arr.size();
vector<int> output(n), count(10, 0);
// 统计当前位数字出现次数
for (int num : arr) count[getDigit(num, exp)]++;
// 计算前缀和
for (int i = 1; i < 10; ++i) count[i] += count[i - 1];
// 放置元素(从后往前保证稳定)
for (int i = n - 1; i >= 0; --i) {
int digit = getDigit(arr[i], exp);
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) {
countSortRadix(arr, exp);
}
}
int main() {
vector<int> arr = {170, 45, 75, 90, 802, 24, 2, 66};
radixSort(arr);
for (int num : arr) cout << num << " "; // 2 24 45 66 75 90 170 802
return 0;
}
三、排序算法对比与选择
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡排序 | 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(nlogn) | O(nlogn) | O(n) | 稳定 | 大规模、需稳定排序 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 | 大规模、平均性能最优 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 大规模、空间受限 |
| 计数排序 | O(n+k) | O(n+k) | O(n+k) | 稳定 | 整数、值范围小 |
| 桶排序 | O(n+nlog(n/m)) | O(n²) | O(n+m) | 稳定 | 元素分布均匀 |
| 基数排序 | O(d*(n+k)) | O(d*(n+k)) | O(n+k) | 稳定 | 整数或字符串、位数固定 |
选择建议:
- 小规模数据(n≤100):插入排序或冒泡排序(实现简单);
- 中大规模数据:快速排序(平均最优)、归并排序(稳定)或堆排序(空间优);
- 特殊场景:值范围小用计数排序,分布均匀用桶排序,整数/字符串用基数排序。