👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:【C/C++】算法
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵
希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
- 一、直接插入排序
- 1.1 基本思想
- 1.2 算法思路
- 1.3 代码实现
- 1.4 特性总结
- 二、希尔排序
- 2.1 希尔排序的由来
- 2.2 算法思路
- 2.3 如何选择gap
- 2.4 希尔排序效率问题
- 2.5 代码实现
- 2.6 希尔排序的特性总结
一、直接插入排序
1.1 基本思想
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
实际中,玩扑克牌时,整理一副牌从小到大或者从大到小就用到了插入排序的思想
1.2 算法思路
首先数据内的第一个元素(下标为0)是不需要排序的。因此,当插入第
i
(i ≥ 1)个元素时,可以拿a[i]
与当前数组内的最后一个元素进行比较,若a[i] < 数组最后一个元素
,则将原来的位置上的元素向后移;若满足条件,则直接插入。
1.3 代码实现
#include <stdio.h>
// n - 数组元素个数
void InsertSort(int* a, int n)
{
//数组第一个元素不需要参与排序
for (int i = 1; i < n; i++)
{
// end - 每一次数组最后一个元素的下标
int end = i - 1;
//InsertNum - 要插入的元素
int InsertNum = a[i];
while (end >= 0)
{
if (a[end] > InsertNum)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
//当循环来到此处有两种可能性
//1.break跳出来的
//2.头插
a[end + 1] = InsertNum;
}
}
int main()
{
int a[] = { 9,4,2,3,10,8,6,1,7,5 };
int ArraySize = sizeof(a) / sizeof(a[0]);
InsertSort(a, ArraySize);
for (int i = 0; i < ArraySize; i++)
printf("%d ", a[i]);
return 0;
}
【运行结果】
1.4 特性总结
- 时间复杂度
最好:原数组已经是按需求有序了,每趟只需与前面的有序元素序列的最后一个元素进行比较,总比较次数为n - 1
,时间复杂度为O(N)
;
最坏:假设要排升序,原数组是逆序的情况下,O(N2)
综上,直接插入排序的时间复杂度为O(N2)- 空间复杂度:
O(1)
- 稳定性:插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。如果碰见一个和插入元素相等的,那么将会把待插入元素放在相等元素的后面。所以,相等元素的相对的前后顺序没有改变,所以插入排序是稳定的。
二、希尔排序
2.1 希尔排序的由来
从直接插入排序中,(目标数组是升序)我们总结了:当原数组是降序的时候,时间复杂度为O(N2),效率极低;当原数组是接近升序的,那么时间复杂度就是O(N),效率最高。因此,又一位名叫希尔的大佬发现,如果一开始就让数组内的元素接近有序的话,插入排序的效率不就大大提升了吗?所以,希尔排序是直接插入排序的优化
2.2 算法思路
- 首先,预排序,目的:让数组内的元素接近有序
如何使数组接近有序?
先选定一个间隔gap
的整数,然后以距离为gap
数进行分组排序,其分组排序还是运用到直接插入排序
- 最后对有序数组进行插入排序即可
当间隔
gap == 1
时,也就是说,距离为1的分为一组,那不就是插入排序
【演示】
(数据来源于后面的代码)
通过以上图片,我们还可以总结一个规律:gap为几,就代表有几组
2.3 如何选择gap
gap
的取法有多种。最初希尔大佬提出取gap = n / 2
,gap =gap / 2
,直到gap =1
,后来 Knuth提出取gap = gap / 3 + 1
,还有人提出取奇数好,也有人提出gap
互质好。但无论哪一种主张都没有得到证明。但是在算法思路中,图中的gap
取值是根据希尔大佬来的,而我在代码实现中给定的是Knuth的方法
2.4 希尔排序效率问题
有人想:希尔排序在预排序的时候不是运用到很多的插入排序,为什么其效率还是比插入排序高?
原因是:其实gap
的取值决定数组内的元素是否接近有序,gap
越大,排的也越快,但越不接近有序;gap
越小,排的也就越慢,但越接近有序。所以一开始gap
的值可以设为数组元素个数(gap一定不可能超过数组元素个数),每次进行/2
,不断缩小gap
,其实最后发现,希尔排序的插入排序的次数其实是小于直接排序的插入次数的。
2.5 代码实现
#include <stdio.h>
//n - 数组元素个数
void ShellSort(int* a, int n)
{
//gap最大也只能取到数组元素个数
int gap = n;
while (gap > 1)
{
//gap的取值无论是多少,最后结果一定是1
//那么当gap=1时,就是最后的插入排序
gap = gap / 3 + 1;
//gap为几,就代表有几组
for (int i = 0; i < gap; i++)
{
//以下代码和插入排序差不多
for (int j = i; j < n - gap; j += gap)
{
int end = j;
int InsertNum = a[end + gap];
while (end >= 0)
{
if (a[end] > InsertNum)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = InsertNum;
}
}
}
}
int main()
{
int a[] = { 9,4,2,3,10,8,6,1,7,5 };
int ArraySize = sizeof(a) / sizeof(a[0]);
ShellSort(a, ArraySize);
for (int i = 0; i < ArraySize; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
【结果展示】
2.6 希尔排序的特性总结
1. 希尔排序是对直接插入排序的优化
2. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算。但是我们可以用代码进行性能测试的对比
如图,当数据个数由10w时,性能结构如下图所示