前言
在本篇博客中,作者将会带领你理解和实现直接插入排序和希尔排序。
一.直接插入排序
1.排序思想
直接插入排序的思想是:在一趟已经有序的序列中插入一个新的数。
就像我们平常玩的扑克牌一样,当我们摸到一张新的牌的时候,需要把它插入到已经有序的扑克牌序列中。
每趟排序的结果,其中end为有序序列的最后一个元素
2.单趟排序
绝大部分排序都可以分为单趟排序和多趟排序,在我们写排序时,可以先写单趟排序,再写多趟排序,这可以使得写起来更加简单易懂。
如下图所示,为单趟排序演示。
①单趟排序代码伪代码实现
int end;//已经有序序列中的最后一个元素
int tmp = arr[end + 1];//待排数据
while (end != -1)//如果end位置到-1,则退出循环
{
if (tmp < arr[end])
{
arr[end + 1] = arr[end];//将end位置的数后移
end--;
}
else//如果待排的数比前面大,则退出循环
{
break;
}
}
arr[end + 1] = tmp;//把待排的tmp放到对应的位置
3.多趟排序
一趟排序是将一个未排的数在有序序列中进行排序,现在的问题是有序序列应该从那开始,
是从第一个元素开始,因为第一个元素可以默认为有序,所以要从第一个开始进行直接插入排序。
所以一趟排序的代码外面还需套一层循环,控制end的位置从那开始。
①多趟排序代码实现
void InserSort(int* arr, int sz)//sz为数组元素个数
{
for (int i = 0; i < sz -1 ; i++)//注意!!! end最后一个位置是倒数第二个
{
int end = i;//已经有序序列中的最后一个元素
int tmp = arr[end + 1];//待排数据
while (end != -1)
{
if (tmp < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
4.时间复杂度和空间复杂度
时间复杂度:
时间复杂度看的是最坏的情况,即,当要排的数是完全逆序的时候的情况。这时数据需要移动的次数是1+2+3+4+……+(n-1),所以时间复杂度是O(n²)
空间复杂度:
在进行排序时,只需要开一些临时变量,如end,tmp等等,没有额外开辟空间,所以空间复杂度为O(1)
二.希尔排序
1.排序思想
希尔排序是对直接插入排序的优化,希尔这个人发现,当一组数据接近有序的时候,再使用直接插入排序会变得非常快(因为数据移动的次数非常少),所以希尔排序的思想是,先对一组数进行预排序,使其接近有序后,在进行直接插入排序。
2.排序流程
选取一个gap值,代表每gap数之间分为一组,排完后,缩小gap值,直到gap值等于1。
如下图:红色线为一组,蓝色线为一组,绿色线为一组。分别对每组进行排序。
3.单趟排序
①单趟排序伪代码
直接插入排序可以看做gap值始终为1的希尔排序,所以希尔排序的代码与直接插入排序非常相似,只不过gap不同而已。
int gap = 3;//gap取一个值
int end;//表示有序数组中最后一个元素
int tmp = arr[end + gap];//待排的数据为end+gap的位置
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];//则将end位置的移动到end+gap
end = end - gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;//把待排的数放到对应的位置
4.多趟排序
①gap值为3的多趟排序
int gap=3;//先默认gap为3
for (int i = 0; i < sz - gap; i++)//对数组进行一次gap=3的排序
{
int end = i;//表示有序数组中最后一个元素
int tmp = arr[end + gap];//待排的数据
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];//则将end位置的移动到end+gap
end = end - gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;//把待排的数放到对应的位置
}
用gap值为3进行一次多趟排序有,只是进行了一次预排序,后面要不断缩小gap的值,直到gap为1。
那么gap的值可以怎么定呢,经过研究发现,gap值一开始等于数组元素个数为开始,后一直缩小到原来的三分之一即可。
②完整希尔排序
void ShellSort(int* arr, int sz)
{
int gap = sz;//gap取一个值
while (gap > 1)
{
gap = gap / 3 + 1;//gap缩小到原来的三分之一
for (int i = 0; i < sz - gap; i++)
{
int end = i;//表示有序数组中最后一个元素
int tmp = arr[end + gap];//待排的数据
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];//则将end位置的移动到end+gap
end = end - gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;//把待排的数放到对应的位置
}
}
}
5.时间复杂度和空间复杂度
希尔排序的时间复杂度计算较为困难,这里直接给出结果:
时间复杂度:O(n的1.3次方)~O(n²)
空间复杂度:O(1)
三.稳定性分析
什么是稳定性?
稳定性是,如果一组数里面有两个相同的数,则排序完成后,如果不会改变他们的相对顺序,则这个排序算法是稳定的,否则是不稳定的。
结论
直接插入排序是稳定的。
希尔排序时不稳定的。
四.所有源代码
#include<stdio.h>
void Print(int* arr, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void InserSort(int* arr, int sz)
{
for (int i = 0; i < sz -1 ; i++)
{
int end = i;//已经有序序列中的最后一个元素
int tmp = arr[end + 1];//待排数据
while (end != -1)
{
if (tmp < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
void ShellSort(int* arr, int sz)
{
int gap = sz;//gap取一个值
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < sz - gap; i++)
{
int end = i;//表示有序数组中最后一个元素
int tmp = arr[end + gap];//待排的数据
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];//则将end位置的移动到end+gap
end = end - gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;//把待排的数放到对应的位置
}
}
}
int main()
{
int arr1[] = { 100,5,48,65,43,2,5,9 };
int arr2[] = { 15, 20, 48, 6, 3, 1 ,4 ,2 };
InserSort(arr1, sizeof(arr1) / sizeof(int));
ShellSort(arr2, sizeof(arr2) / sizeof(int));
Print(arr1, sizeof(arr1) / sizeof(int));
Print(arr2, sizeof(arr2) / sizeof(int));
return 0;
}