算法之十大经典排序(C++版)
1.快速排序
思想:分治法 + 递归
-
选取基准元素(pivot)
-
将数组分为两部分:小于基准的放左边,大于基准的放右边
-
递归地对左右子数组进行相同操作
void quick_sort(vector<int> &nums, int left, int right)
{
if (left + 1 >= right)
{
return;
}
int first = left, last = right - 1, key = nums[first];
while (first < last)
{
while (first < last && nums[last] >= key)
{
--last;
}
nums[first] = nums[last];
while (first < last && nums[first] <= key)
{
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, left, first);
quick_sort(nums, first + 1, right);
}
2.插入排序
思想:构建有序序列
-
从第一个元素开始,视为已排序
-
取下一个元素,在已排序序列中从后向前扫描
-
找到合适位置插入,重复直到所有元素有序
特点:对小规模数据高效,适合基本有序的数据
void insert_sort(vector<int>& nums, int n)
{
for (int i = 0; i < n; ++i)
{
for (int j = i; j > 0 && nums[j] < nums[j - 1]; --j)
{
swap(nums[j], nums[j - 1]);
}
}
}
3.合并排序
思想:分治法 + 合并有序数组
-
递归地将数组二分为子数组直到单个元素
-
合并相邻子数组:比较两个数组头部元素,取较小者放入新数组
-
重复合并直到整个数组有序
优势:稳定排序,时间复杂度稳定O(nlogn)
void merge_sort(vector<int>& nums, int l, int r, vector<int> & temp)
{
if (l + 1 >= r)
{
return;
}
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
int p = l, q = m, i = l;
while (p < m || q < r)
{
if (q >= r || (p < m && nums[p] <= nums[q]))
{
temp[i++] = nums[p++];
}
else {
temp[i++] = nums[q++];
}
}
for (int i = l; i < r; ++i)
{
nums[i] = temp[i];
}
}
4.冒泡排序
思想:重复交换相邻逆序元素
-
从数组起始位置开始
-
比较相邻元素,若逆序则交换
-
每轮将最大元素"冒泡"到末尾
-
重复n-1轮
优化:设置标志位检测提前完成
void bubble_sort(vector<int>& nums, int n)
{
bool flag;
for (int i = 1; i < n; ++i)
{
flag = false;
for (int j = 1; j < n - i - 1; ++j)
{
if (nums[j] < nums[j - 1])
{
swap(nums[j], nums[j - 1]);
flag = true;
}
}
if (!flag)
{
break;
}
}
}
5.选择排序
思想:每次选择最小元素
-
在未排序序列中找到最小元素
-
将其与未排序序列的首元素交换
-
重复直到所有元素有序
特点:交换次数少(O(n)),但比较次数多
void selection_sort(vector<int>& nums, int n)
{
int mid;
for (int i = 0; i < n - 1; ++i)
{
mid = i;
for (int j = i + 1; j < n; ++j)
{
if (nums[j] < nums[mid])
{
mid = j;
}
}
swap(nums[mid], nums[i]);
}
}
6.桶排序
思想:分散-收集 + 子排序
-
设置固定数量的空桶
-
将元素分配到对应的桶中(映射函数)
-
对每个非空桶进行排序(通常用插入排序)
-
按顺序合并所有桶
适用:均匀分布的数据
void bucketSort(vector<int>& nums) {
if (nums.empty()) return;
// Step 1: 找最大最小值,确定桶的数量
int min_val = *min_element(nums.begin(), nums.end());
int max_val = *max_element(nums.begin(), nums.end());
// Step 2: 确定每个桶的范围和数量
int bucket_size = 5; // 可调参数,控制每个桶能装多少个数
int bucket_count = (max_val - min_val) / bucket_size + 1;
vector<vector<int>> buckets(bucket_count);
// Step 3: 将数据放入对应的桶中
for (int num : nums) {
int index = (num - min_val) / bucket_size;
buckets[index].push_back(num);
}
// Step 4: 每个桶内部排序,并合并到原数组
int idx = 0;
for (auto& bucket : buckets) {
sort(bucket.begin(), bucket.end()); // 可换成插入排序等
for (int num : bucket) {
nums[idx++] = num;
}
}
}
7.希尔排序
思想:改进的插入排序
-
定义增量序列(如n/2, n/4,...1)
-
按当前增量分组,对每组进行插入排序
-
增量逐步缩小,最后增量=1时整体排序
优势:突破O(n²)限制
void shellSortCore(vector<int>& nums, int gap, int i) {
int inserted = nums[i];
int j;
// 插入的时候按组进行插入
for (j = i - gap; j >= 0 && inserted < nums[j]; j -= gap) {
nums[j + gap] = nums[j];
}
nums[j + gap] = inserted;
}
void shellSort(vector<int>& nums) {
int len = nums.size();
//进行分组,最开始的时候,gap为数组长度一半
for (int gap = len / 2; gap > 0; gap /= 2) {
//对各个分组进行插入分组
for (int i = gap; i < len; ++i) {
//将nums[i]插入到所在分组正确的位置上
shellSortCore(nums,gap,i);
}
}
for (auto a : nums) {
cout << a << "";
}
}
8.堆排序
思想:利用堆数据结构
-
构建最大堆(父节点 > 子节点)
-
将堆顶元素(最大值)与末尾元素交换
-
减少堆大小,重新调整堆
-
重复直到堆大小为1
特点:原地排序,O(1)空间
void heapify(vector<int>& nums, int n, int i)//对有一定顺序的堆,
//当前第i个结点取根左右的最大值(这个操作称heapfiy)
{
int l = i * 2 + 1, r = i * 2 + 2;
int max = i;
if (l<n && nums[l]>nums[max])
max = l;
if (r<n && nums[r]>nums[max])
max = r;
if (max != i)
{
swap(nums[max], nums[i]);
heapify(nums, n, max);
}
}
void heapify_build(vector<int>& nums, int n)//建立大根堆,从树的倒数第二层第一个结点开始,
//对每个结点进行heapify操作,然后向上走
{
int temp = (n - 2) / 2;
for (int i = temp; i >= 0; i--)
heapify(nums, n, i);
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
void heapify_sort(vector<int>& nums, int n)//建立大根堆之后,每次交换最后一个结点和根节点(最大值),
//对交换后的根节点继续进行heapify(此时堆的最后一位是最大值,因此不用管他,n变为n-1)
{
heapify_build(nums, n);
for (int i = 0; i < n; i++)
{
swap(nums.front(), nums[n - i - 1]);
heapify(nums, n - i - 1, 0);
}
}
9.计数排序
思想:统计元素频次
-
找出数组中最大最小值
-
创建计数数组,统计每个元素出现次数
-
累加计数数组(得到元素位置信息)
-
反向填充目标数组
要求:整数排序,数据范围不大
// 计数排序
void CountSort(vector<int>& vecRaw, vector<int>& vecObj)
{
// 确保待排序容器非空
if (vecRaw.size() == 0)
return;
// 使用 vecRaw 的最大值 + 1 作为计数容器 countVec 的大小
int vecCountLength = (*max_element(begin(vecRaw), end(vecRaw))) + 1;
vector<int> vecCount(vecCountLength, 0);
// 统计每个键值出现的次数
for (int i = 0; i < vecRaw.size(); i++)
vecCount[vecRaw[i]]++;
// 后面的键值出现的位置为前面所有键值出现的次数之和
for (int i = 1; i < vecCountLength; i++)
vecCount[i] += vecCount[i - 1];
// 将键值放到目标位置
for (int i = vecRaw.size(); i > 0; i--) // 此处逆序是为了保持相同键值的稳定性
vecObj[--vecCount[vecRaw[i - 1]]] = vecRaw[i - 1];
}
10.基数排序
思想:按位分配收集
-
从最低位到最高位(LSD)或反之(MSD)
-
按当前位值分配到0-9的桶中
-
按桶顺序收集元素
-
重复直到所有位处理完毕
适用:整数或字符串排序
// 辅助函数:求最大位数
int maxBit(const vector<int>& nums) {
int maxNum = nums[0];
for (int num : nums) {
if (num > maxNum)
maxNum = num;
}
int d = 0;
while (maxNum > 0) {
maxNum /= 10;
++d;
}
return d;
}
// 基数排序(LSD - 低位优先)
void radixSort(vector<int>& nums) {
int n = nums.size();
if (n == 0) return;
int d = maxBit(nums); // 获取最大位数
vector<int> tmp(n); // 临时数组
vector<int> count(10); // 计数器,0~9 的桶
int radix = 1; // 当前位权值(1, 10, 100...)
// 对每一位进行排序
for (int i = 0; i < d; ++i) {
// 1. 初始化计数器
fill(count.begin(), count.end(), 0);
// 2. 统计每个桶中的元素个数
for (int num : nums) {
int k = (num / radix) % 10;
count[k]++;
}
// 3. 将 count 转换为前缀和,表示每个桶的结束位置
for (int j = 1; j < 10; ++j) {
count[j] += count[j - 1];
}
// 4. 从后往前放入 tmp(保持稳定性)
for (int j = n - 1; j >= 0; --j) {
int k = (nums[j] / radix) % 10;
tmp[count[k] - 1] = nums[j];
count[k]--;
}
// 5. 将排序后的 tmp 拷贝回 nums
nums = tmp;
// 下一位
radix *= 10;
}
}
11.测试方法
数组各数值可自行改变:
int main()
{
int n;
vector<int> vec = { 14, 1, 5, 18, 9, 26, 18 };
n = vec.size();
vector<int> temp(n, 0);
#if 0
vector<int> vec = { 170, 45, 75, 90, 802, 24, 2, 66 };
int n = vec.size();
vector<int> temp(n, 0);
//手动输入vec
vector<int> num;
int n;
cout << "input n";
cin >> n;
for (int i = 0; i < n; ++i)
{
int p;
cin >> p;
num.push_back(p);
}
Print(num);
//桶排序
bucket_sort(vec);
Print(vec);
//sort(快排)
sort(vec.begin(), vec.end());
Print(vec);
//quick_sort
quick_sort(vec, 0, n);
Print(vec);
//merge_sort
vector<int> temp(vec.size());
merge_sort(vec, 0, n, temp);
Print(vec);
//insert_sort
insert_sort(vec, n);
Print(vec);
//bubble_sort
bubble_sort(vec, n);
Print(vec);
//selections_sort
selection_sort(vec, n);
Print(vec);
//radix_sort
radix_sort(vec, n);
Print(vec);
//count_sort
count_sort(vec, temp, n);
Print(temp);
//堆排序
heap_sort(vec, n);
Print(vec);
#endif
return 0;
}
12.算法特性对比表
| 排序算法 | 平均时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| 快速排序 | O(n log n) | O(log n) | 不稳定 | 通用排序,大规模数据 |
| 插入排序 | O(n²) | O(1) | 稳定 | 小规模或基本有序数据 |
| 归并排序 | O(n log n) | O(n) | 稳定 | 链表排序,外部排序 |
| 冒泡排序 | O(n²) | O(1) | 稳定 | 教学用途,小规模数据 |
| 选择排序 | O(n²) | O(1) | 不稳定 | 交换成本高的场景 |
| 桶排序 | O(n + k) | O(n + k) | 稳定 | 均匀分布的浮点数 |
| 希尔排序 | O(n log n) | O(1) | 不稳定 | 中等规模数据 |
| 堆排序 | O(n log n) | O(1) | 不稳定 | 需要原地排序且O(1)空间 |
| 计数排序 | O(n + k) | O(k) | 稳定 | 整数排序,范围小 |
| 基数排序 | O(d(n + k)) | O(n + k) | 稳定 | 多位数排序,字符串字典序排序 |
注:
-
k: 桶的数量或计数范围
-
d: 数字的最大位数
-
稳定性:相等元素排序后相对位置不变
-
实际应用中快速排序、归并排序、堆排序最常用
更多推荐
所有评论(0)