🕒 1. 直接插入排序
💡 算法思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。就像玩扑克牌时,对其进行从小到大排序。
代码实现如下:
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++) // 因为x元素位置是i的下一个位置,为防止x越界,需要使 i < n - 1
{
int end = i; // 当前要插入位置的索引
int tmp = a[end + 1]; // 需要插入的元素值
// 将 tmp 插入到合适的位置
while (end >= 0 && a[end] > tmp)
{
a[end + 1] = a[end]; // 如果当前元素大于 tmp,则后移
--end;
}
a[end + 1] = tmp; // 插入 tmp 到正确位置
}
}
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高。
- 最坏时间复杂度(逆序):O(N2)
最好时间复杂度(顺序):O(N) - 空间复杂度:O(1)
- 稳定性:稳定
🕒 2. 希尔排序(缩小增量排序)
💡 算法思想:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的元素进行排序。然后将gap逐渐减小重复上述分组和排序的工作。当到达gap=1时,所有元素在统一组内排好序。
算法拆解:预排序+直接插入排序
预排序的本质也是插入排序,是对每一小组中的数据进行插入排序。目的是让整个数组接近有序,使得一开始无序的数组趋向于让大的数往后面走,小的数往前面走;在经过预排序使得整个数组接近有序后,就可以更加快速(中间也减少了数据的挪动次数)的将数组进行排序。
代码实现如下:
void ShellSort(int* a, int n)
{
int gap = n; // 初始增量设置为数组长度n
// 当增量大于1时继续排序
while (gap > 1)
{
// 使用动态增量序列 gap = gap / 3 + 1,这种增量选择时间复杂度约为O(N^1.3)
gap = gap / 3 + 1;
// 对每个子序列进行插入排序,步长为 gap
for (int i = 0; i < n - gap; ++i)
{
int end = i; // 当前要插入位置的索引
int tmp = a[end + gap]; // 要插入的元素
// 使用插入排序的方式将 tmp 插入到合适位置
while (end >= 0 && a[end] > tmp)
{
a[end + gap] = a[end]; // 如果前一个元素比 tmp 大,则向后移动 gap 个位置
end = end - gap; // 继续向前比较
}
a[end + gap] = tmp; // 将 tmp 插入到正确的位置
}
}
}
希尔排序的特性总结:
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
- 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此希尔排序的时间复杂度都不固定,这里就不深究了,感兴趣可看下图。
- 时间复杂度O(N1.5)
- 空间复杂度O(1)
- 稳定性:不稳定
❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页