说起希尔排序,我们就不得不说一下直接插入排序了。因为只有理解了直接插入排序,才能更好的理解希尔排序。
扑克牌是我们几乎每个人都可能玩过的游戏。最基本的扑克玩法都是一边摸牌,一边理牌。假如我们拿到了这样一手牌,如图所示,啊,似乎是同花顺呀,别急,我们得先理一理顺序才知道是否是真的同花顺。请问,如果是你,你会怎样理牌呢?
应该说,哪怕你是第一次玩牌,只要是认识这些数字,理牌的方法都是不用教的。将牌按照指定的顺序依次插入即可。我们在这里用到的理牌的方法,就是直接插入排序法。
直接插入排序
基本思想
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
请看动图演示:
当插入第 end (end>=1)个元素时,前面的array[0],array[1],…,array[end-1]已经排好序,此时用array[end]的排序码与array[end-1],array[end-2],…的排序码顺序进行比较,找到插入位置即将array[end]插入,原来位置上的元素顺序后移 。
代码实现
//先进行单趟排序
int end;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
break;
}
arr[end + 1] = tmp;
结合图示理解:
在进行整体排序:
#include <stdio.h>
void InsertSort(int* arr, int sz)
{
for (int i = 0; i < sz - 1; i++)
{
//让end从0开始排
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
//只要比tmp大,就往后挪动。同时end减1
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
break;
}
arr[end + 1] = tmp;
}
}
int main()
{
int arr[] = { 9,1,5,8,3,7,4,6,2,10 };
InsertSort(arr, sizeof(arr) / sizeof(arr[0]));
//结果为:1,2,3,4,5,6,7,8,9,10
return 0;
}
参考代码已奉上,希望大家能够理解。
复杂度分析
直接插入排序从空间上看,它只需要一个记录的辅助空间,因此关键是看它的时间复杂度。
在最好的情况下,也就是要排序的序列本身就是有序的,比如纸牌拿到后就是[2,3,4,5,6],那么我们就不需要插入,时间复杂度为O(N)。
在最坏的情况下,也就是要排序的序列本身是逆序的,比如[6,5,4,3,2],此时每一张牌我们都需要插入,时间复杂度为O(N^2)。
所以直接插入排序的时间复杂度为:O(N^2)
希尔排序
终于到我们的重头戏了。有了前面直接插入排序的理解,我们就可以直接看希尔排序了。
基本介绍
希尔排序是D.L.Shell于1959年提出来的一种排序算法,在这之前排序算法的时间复杂度基本上都是O(N^2)的,希尔排序算法是突破这个时间复杂度的第一批算法之一
基本思想
先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当选定的整数=1时,所有记录在统一组内排好序。
请看图示:
注意:希尔排序是对直接插入排序的优化。 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样不就是直接插入排序的最好情况吗?这样整体而言,就可以达到优化的效果。
ps.所谓预排序,就是小的数据基本在前面,大的基本在后面,不大不小的基本在中间。
代码实现
老样子,我们先看单趟排序
int end;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
break;
}
arr[end + gap] = tmp;
咦 ~ 大家有没有发现希尔排序的单趟排,与直接插入排序的单趟排代码基本一样。是的,的确是这样,上面也说了,希尔排序就是直接插入排序的优化。这里就不做图示了,希望大家能够理解。我们再来看看整体排序:
void ShellSort(int* arr, int sz)
{
int gap = sz;
while (gap > 1)
{
gap = gap / 2;
for (int i = 0; i < sz - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
break;
}
arr[end + gap] = tmp;
}
}
}
与插入排序不同的是,插入排序的end每次走1步,而希尔排序的end每次走gap步。那么问题又来了,gap为多少时效果最好呢?这个数学难题迄今为止还没有人能够解决。不过,不管gap的值是多少,都要满足最后一次 gap == 1。另外由于是跳跃式移动,希尔排序并不是一种稳定的排序算法。