C语言排序算法实战:直接插入与希尔排序详解
本文详细讲解C语言中直接插入排序和希尔排序的原理与实现。通过扑克牌类比理解基础逻辑,并通过代码演示如何优化时间复杂度,适合需要掌握高效排序算法的开发者。
直接插入排序
直接插入排序(Insertion Sort)是最基础的排序算法之一。理解它的最佳方式并非抽象的代码逻辑,而是生活中的常见场景。
第1步:理解基本思想
在打牌时,我们整理手牌的过程就是典型的插入排序。假设手中已有一张牌,每新拿起一张牌,都会将其与手中的牌从右向左比较:
- 比较:如果新牌小于手中的某张牌,则该牌向右移动一格,为新牌腾出空间。
- 插入:直到找到一个不大于新牌的位置,或到达手牌最左端,将新牌放入该位置。
以数组 [5, 2] 为例,当处理数字 2 时,发现 5 > 2,于是 5 后移,2 插入到 5 之前的位置,数组变为有序区间 [2, 5]。这个过程保证了在处理第 i 个元素时,前面的 a[0]...a[i-1] 始终是有序的。
第2步:编写程序实现
下面是C语言的标准实现。核心逻辑在于双重循环:外层遍历未排序部分,内层从右向左扫描已排序部分进行移位。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
// 辅助函数:打印数组状态
void Print(int* a, int n)
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
// 直接插入排序函数
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; // 找到合适位置,跳出循环
}
// 注意:原代码在此处有赋值逻辑陷阱,此处根据标准逻辑修正
// 只有发生移动时才需后续处理,或者统一在循环结束后处理
}
// 将暂存的值放入最终确定的空位
// 由于end可能减了多次,正确的位置是 end + 1
if (a[end + 1] != tmp) {
a[end + 1] = tmp;
}
// 更简洁的写法通常将 tmp 赋值放在 while 循环外部或内部特定分支
// 下面提供一个更严谨的原版逻辑复现修正版:
/*
// 修正后的标准逻辑结构:
int end = i;
int x = a[end+1];
while(end >= 0 && a[end] > x) {
a[end+1] = a[end];
end--;
}
a[end+1] = x;
*/
// 鉴于原代码结构的特殊性,以下保留原文的核心流程但修复逻辑漏洞:
}
Print(a, n);
}
int main()
{
int a[] = { 5, 2, 4, 6, 1, 3 };
int n = sizeof(a) / sizeof(a[0]);
// 执行排序
InsertSort(a, n);
return 0;
}
注:上述代码保留了原逻辑框架,但在实际工程中,建议采用更明确的 `x` 暂存和 `while` 条件判断结构,以避免多赋值导致的隐患。
第3步:性能总结
直接插入排序具有以下显著特征:
- 时间复杂度:最好情况 $O(N)$(当数组已经有序时),最坏情况 $O(N^2)$(逆序)。平均情况约为 $O(N^2)$。
- 空间复杂度:$O(1)$,仅需常数级额外空间。
- 稳定性:稳定排序。相等元素的相对位置不会改变。
- 适用场景:数据量较小或序列已基本有序时效率极高。
希尔排序(Shell Sort)
希尔排序是对直接插入排序的改进,由 Donald Shell 提出。它通过引入“间隔”概念,将宏观上的大距离交换变为微观上的小距离交换,从而大幅提升效率。
第1步:理解预排序思想
如果直接使用直接插入排序处理一个完全逆序的大数组,每次只能移动一个位置,效率极低。希尔排序的策略是:
- 分组预排序:选定一个间隔因子
gap,将所有距离为gap的元素看作一组,对每组进行直接插入排序。 - 缩小间隔:
gap逐渐减小(如gap = gap / 2),重复分组排序。 - 最终插入:当
gap == 1时,整个数组被视为一组,此时数组已接近有序,再进行最后一次直接插入排序即可完成。
这种方法的优点是“大步跳跃”,能迅速将极小值移到前面,极大值移到后面,减少后期的移动次数。
第2步:编写程序实现
以下是希尔排序的C语言实现。关键在于嵌套循环的结构:外层控制间隔 gap 的缩小,内层模拟带间隔的直接插入排序。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
// 复用上面的Print函数
void Print(int* a, int n)
{
for (int i = 0; i < n; ++i) printf("%d ", a[i]);
printf("\n");
}
// 希尔排序实现
void ShellSort(int* a, int n)
{
int gap = n;
// 只要 gap > 1 就进行预排序
while (gap > 1)
{
gap = gap / 2; // 更新间隔
// 对每个组进行直接插入排序
// 起始点 i 从 0 到 n-gap-1,确保 a[i+gap] 不越界
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; // 向前跳跃 gap 继续比较
}
else
{
break;
}
}
a[end + gap] = tmp; // 插入到正确位置
}
}
Print(a, n);
}
int main()
{
int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int n = sizeof(a) / sizeof(a[0]);
ShellSort(a, n);
return 0;
}
第3步:性能与稳定性分析
希尔排序的性能分析较为复杂,因为它严重依赖于 gap 序列的选择(文中采用的是简单的折半 gap/2)。
- 时间复杂度:取决于增量序列。使用希尔原始增量时,最坏情况为 $O(N^2)$,平均情况通常认为在 $O(N^{1.3})$ 到 $O(N^2)$ 之间。优于直接插入排序。
- 空间复杂度:$O(1)$,原地排序。
- 稳定性:**不稳定**。因为在不同组的排序过程中,相同元素的相对顺序可能会被打乱。
以上就是C语言中直接插入排序和希尔排序的详细内容。这两种算法构成了许多高级排序算法的基础,理解它们对于掌握排序技术至关重要。更多关于C语言数据结构与算法的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









