
个人主页 : 流年如梦
专栏 : 《零基础轻松入门C语言》 《数据结构:从入门到掌握》
文章目录
前言
排序是数据结构与算法中最基础、最高频、面试必问 的内容,无论是日常开发、刷题、考试,还是底层系统设计,都离不开排序。本文根据经典学习笔记整理,涵盖插入、选择、交换、归并、非比较 五大类共八种排序,包含核心思想、完整代码、复杂度、稳定性、适用场景、易错点,一站式学会所有排序
一.排序基础概念
排序 :将一组数据按关键字递增或递减排列
稳定性 :相同值在排序后相对顺序不变 ,则为稳定排序
内部排序 :数据在内存中排序(👉本篇文章讲的就是内部排序 )
外部排序:数据太大,内存放不下,需要外存(如归并外排序)
二.排序算法分类
- 插入排序 --> 直接插入排序、希尔排序
- 选择排序 --> 直接选择排序、堆排序
- 交换排序 --> 冒泡排序、快速排序
- 归并排序
- 非比较排序 --> 计数排序
三.八大排序
3.1直接插入排序
像整理扑克牌一样,把待排序数据逐个插入到前面已经有序的序列 中,直到全部插完

c
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
🧐分析 :
时间复杂度O(N²),空间复杂度O(1)
稳定性 --> 稳定
适用场景 --> 数据量小、基本有序
3.2希尔排序(缩小增量排序)
对插入排序的优化(通常是gap=n/3+1):
- 选增量
gap,分组进行插入排序 - 不断缩小
gap - 最后
gap=1,就是直接插入排序

c
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
🧐分析 :
时间复杂度O(N¹·³) ~ O(N²),空间复杂度O(1)
稳定性 --> 不稳定
适用场景 --> 中等数据量,效率远高于直接插入
3.3直接选择排序
每一轮选出最小或最大值,放到对应位置,直到有序
c
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
if (a[i] > a[maxi])
maxi = i;
}
if (begin == maxi)
maxi = mini;
Swap(&a[mini], &a[begin]);
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
🧐分析 :
时间复杂度O(N²),空间复杂度O(1)
稳定性 --> 不稳定
适用场景 --> 理解思路,几乎不用
3.4堆排序
利用完全二叉堆 选择数据(👉点击转跳二叉树详解):
- 升序 --> 建大堆
- 降序 --> 建小堆

c
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
child++;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
🧐分析 :
时间复杂度O(NlogN),空间复杂度O(1)
稳定性 --> 不稳定
适用场景 --> 海量数据、优先级队列、Top-K问题
3.5冒泡排序
相邻元素两两比较,大的往后"冒",可以提前优化结束

c
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int exchange = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
Swap(&a[j], &a[j + 1]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
🧐分析 :
时间复杂度O(N²),最好为O(N),空间复杂度O(1)
稳定性 --> 稳定
适用场景 --> 教学演示、简单理解
3.6快速排序
分治法:
- 选基准值
key - 小的放左,大的放右
- 递归左右区间
三种写法:Hoare、挖坑法、前后指针(最常用)
如下为**前后指针(双指针)**代码
c
int Partion(int* a, int left, int right)
{
int prev = left;
int cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[prev], &a[cur]);
cur++;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = Partion(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
🧐分析 :
时间复杂度O(NlogN),最坏为O(N²)(有序数据),空间复杂度O(logN)
稳定性 --> 不稳定
适用场景 --> 实际最快、最常用的通用排序
3.7归并排序
分治法:
- 拆分数组至单个元素
- 两两合并有序数组
c
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = (left + right) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
for (int i = left; i <= right; i++)
a[i] = tmp[i];
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
🧐分析 :
时间复杂度O(NlogN),空间复杂度O(N)
稳定性 --> 稳定
适用场景 --> 链表排序、外部排序、要求稳定且要效率
3.8计数排序(非比较排序)
统计每个数字出现次数,按范围回写,只适用于整数、范围集中
c
void CountSort(int* a, int n)
{
int min = a[0], max = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] < min) min = a[i];
if (a[i] > max) max = a[i];
}
int range = max - min + 1;
int* count = (int*)calloc(range, sizeof(int));
for (int i = 0; i < n; i++)
count[a[i] - min]++;
int j = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
a[j++] = i + min;
}
free(count);
}
🧐分析 :
时间复杂度O(N+range),空间复杂度O(range)
稳定性 --> 稳定
适用场景 --> 年龄、分数、成绩、范围集中整数
四.排序复杂度与稳定性总结表
| 排序 | 平均 | 最好 | 最坏 | 空间 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡 | O(N2) | O(N) | O(N2) | O(1) | 稳定 |
| 直接插入 | O(N2) | O(N) | O(N2) | O(1) | 稳定 |
| 直接选择 | O(N2) | O(N2) | O(N2) | O(1) | 不稳定 |
| 希尔 | O(N1.3) | O(N) | O(N2) | O(1) | 不稳定 |
| 堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
| 快速排序 | O(NlogN) | O(NlogN) | O(N2) | O(logN) | 不稳定 |
| 归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
| 计数排序 | O(N+range) | O(N+range) | O(N+range) | O(range) | 稳定 |
五.排序算法适用场景速选
- 数据小、基本有序 --> 直接插入排序
- 追求最快、通用场景 --> 快速排序
- 要求稳定、效率高 --> 归并排序
- 整数、范围集中 --> 计数排序
- 海量数据、内存有限 --> 堆排序
- 链表排序 --> 归并排序
- 教学演示、最简单 --> 冒泡、选择
🎯总结
排序是算法基石,快排、堆排、归并是面试三巨头
- 简单排序:冒泡、插入、选择,好理解但效率低
- 高效排序:快排、堆排、归并 ,
O(NlogN) - 特殊排序:计数排序,非比较、极快但场景受限
⚠️易错点
- 快排 在有序或重复数据多会退化到
O(N^2)- 堆排序升序必须建大堆,千万不要搞反
- 直接选择 要注意
maxi与begin重叠问题- 归并排序 需要额外
O(N)空间- 希尔排序
gap最后必须等于1- 计数排序数据范围太大会浪费大量内存
- 稳定性:选择、希尔、堆、快排都不稳定
👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步