C语言基础排序算法详解:插入、选择与冒泡
本文深入解析C语言中的基础排序算法,涵盖直接插入排序、希尔排序、直接选择排序、堆排序及冒泡排序。详细阐述各算法的核心思想、代码实现逻辑及复杂度分析,帮助读者理解算法原理并掌握实际编码技巧。
排序的概念及其应用场景
排序的定义与稳定性
排序是将一组记录按照关键字的大小进行递增或递减排列的操作。理解排序时需关注以下核心概念:
- 稳定性:若待排序序列中存在多个相同关键字的记录,排序后它们的相对次序保持不变,则该算法是稳定的;否则为不稳定。
- 内部排序 vs 外部排序:数据全部在内存中处理的称为内部排序;数据量过大需借助外存辅助的称为外部排序。
排序的实际运用
排序算法广泛应用于各类场景,例如高校排名等基于指标数据的整理。正确理解排序逻辑是后续学习复杂数据结构的基础。
插入排序
第1步:直接插入排序
目的:将未排序元素逐个插入到已有序的子序列中,形成新的有序序列。
操作逻辑:
- 从第二个元素开始遍历数组。
- 保存当前待插入元素
x。 - 将有序序列中大于
x的元素依次向后移动。 - 将
x插入到正确位置。
// 直接插入排序
void InsertSort(int* a, int n)
{
assert(a); // 确保传入数组指针有效
int i;
// 从第二个元素开始遍历
for (i = 0; i < n - 1; i++)
{
int end = i;
int x = a[end + 1]; // 保存当前待插入的值
// 向前查找插入位置,大数后移
while (end >= 0)
{
if (a[end] > x) // 升序判断
{
a[end + 1] = a[end];
end--;
}
else
{
break; // 找到合适位置
}
}
a[end + 1] = x; // 插入元素
}
}
第2步:希尔排序
目的:通过分组预排序缩小增量,使数组整体更接近有序,降低直接插入排序的移动次数。
操作逻辑:
- 设定初始间隔
gap,通常为数组长度的一半。 - 对间隔为
gap的子序列进行直接插入排序。 - 缩小
gap(通常除以2),重复上述过程,直到gap == 1。
// 希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2; // 缩小增量
for (int i = 0; i < n - gap; i++)
{
int end = i;
int x = a[end + gap]; // 保存带插入值
// 组内直接插入逻辑
while (end >= 0)
{
if (a[end] > x) // 升序
{
a[end + gap] = a[end];
end -= gap; // 按间隔移动
}
else
break;
}
a[end + gap] = x; // 插入元素
}
}
}
选择排序
第3步:直接选择排序
目的:每轮从未排序部分选出最小(或最大)元素,放到已排序部分的末尾。
代码实现:
// 选择排序
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1; // 左右边界索引
while (begin < end)
{
int mini = begin;
// 遍历找出最小值的下标
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
}
Swap(&a[begin], &a[mini]); // 交换到当前位置
begin++; // 缩小范围
}
}
性能总结:时间复杂度 O(N²),空间复杂度 O(1),不稳定。由于效率较低,生产环境中不推荐使用。
第4步:堆排序
目的:利用堆数据结构高效地选择最大值或最小值。
核心原则:
- 建堆方向:排升序需建立大堆(父节点大于子节点),排降序建立小堆。
- 排序过程:堆顶元素即为最大值,将其与堆尾元素交换,缩小堆的范围,并对新的堆顶执行向下调整。
向下调整算法
假设左右子树已满足堆结构,从父节点开始,与较大的子节点比较并交换,直至满足堆性质。
完整代码实现
// 向下调整:parent为父节点下标,n为堆大小
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[parent] < a[child])
{
Swap(&a[parent], &a[child]);
parent = child; // 更新父节点位置
child = parent * 2 + 1; // 更新子节点位置
}
else
{
break; // 堆结构已恢复
}
}
}
// 堆排序 (升序) -> 建大堆
void HeapSort(int* a, int n)
{
int i;
// 1. 建堆:从最后一个非叶子节点开始向下调整
for (i = (n - 1 - 1) / 2; i >= 0; i--) // 修正公式: (parent-1)/2 对应倒数第二个元素的父节点,此处取(n-2)/2即(n-1-1)/2
{
Adjustdown(a, n, i);
}
// 2. 排序:交换堆顶与堆尾,调整堆顶
for (i = n - 1; i >= 0; i--)
{
Swap(&a[0], &a[i]); // 将最大值移至末尾
Adjustdown(a, i, 0); // 剩余 i 个元素重新调整为堆
}
}
性能总结:时间复杂度 O(N·logN),空间复杂度 O(1),不稳定。相比直接选择排序效率显著提升。
交换排序之冒泡排序
冒泡排序逻辑
目的:通过相邻元素的比较和交换,将最大(或最小)元素“冒泡”至序列末端。
操作细节:
- 外层循环控制趟数,共需
n-1趟。 - 内层循环控制每趟的比较次数,每趟结束后末尾元素就位,减少一次比较。
- 若相邻元素逆序,则交换它们。
代码实现
// 冒泡排序
void BubbleSort(int* a, int n)
{
int i, j;
// n-1 趟
for (i = 0; i < n - 1; i++)
{
// 每趟需要比较的次数随趟数减少
for (j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j + 1]) // 升序:前大于后则交换
{
Swap(&a[j], &a[j + 1]);
}
}
}
}
总结
本文梳理了C语言中几种基础的排序算法。直接插入排序适合小规模或部分有序的数据;希尔排序通过增量优化提升了插入排序的效率;堆排序利用完全二叉树结构实现了 O(N·logN) 的高效排序;而冒泡排序虽然简单但效率较低,通常用于教学演示。后续将介绍快速排序和归并排序等更高效的算法。以上就是C语言基础排序算法的详细解析,更多关于排序算法优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













