比较类排序系列-希尔排序
1. 原理
希尔排序是对插入排序的扩展。插入排序是对所有数据进行排序,而希尔排序会把数据分成多个分组,每组数据内部进行插入排序,这样的分组插入排序会进行多次,直到最后所有数组都归为一组,整体再进行一次插入排序,则排序完成。假设间隔为gap, 则希尔排序会把数据中任意间隔为gap的数据划分为一组数据,这样就会有gap个分组,每个分组进行插入排序。gap不断的从大到小变化,直到gap为1时,就退化为基本的插入排序。
算法过程如下图所示
- 从上图可以看到,每一遍中,相同颜色的数据被分成了一组,组内进行的为插入排序。上图演示了gap初始值从5开始,第一遍排序中,被分成了5组数据,每组数据只包含2个数据,这5组数据分别进行插入排序。第二遍排序中,gap为2, 被分成了2组数据,同样的2组数据分别进行插入排序。第三遍排序中,gap为1, 此时就退化成基本的插入排序,所有数据为一组,进行插入排序,最终整个数据有序。需要注意的是,最后一遍排序,gap必须为1,这样才能保证整个数据有序。
- 在这里希尔排序相当于做了多次的插入排序,而当gap不为1时,经过插入排序之后,整体还没有达到有序,但是数据在慢慢的接近有序。所以希尔排序是对数据先做预排序,待数据接近有序之后,再进行插入排序就比较快了。
- 希尔排序快的原因在于,它让数据移动的距离更远,不像插入排序,数据只是在相邻的位置之间移动,从整体上减少了数据移动的次数,进而提高了排序的性能。
1.1gap的变化
一般gap的变化是有规律的,gap的初始值一般是数据长度的二分之一或者三分之一,之后每次减少二分之一或者三分之一,直到gap为1,执行完最后一次插入排序之后,完成排序。
2. 代码
void shellSort(int* array, int n)
{
int gap = n;
while (gap > 1)
{
//对数据进行分组
//每组数据进行插入排序: 间隔为gap的为同一组数据
gap = gap / 3 + 1;
// gap > 1: 预排序
for (int i = 0; i < n - gap; ++i)
{
int end = i;
//间隔为gap的为同一组数据
int key = array[end + gap];
while (end >= 0 && array[end] > key)
{
array[end + gap] = array[end];
end -= gap;
}
array[end + gap] = key;
}
}
}
public static void shellSort(int[] arr){
//对数据进行分组
//每组数据进行插入排序: 间隔为gap的为同一组数据
int gap = arr.length;
while(gap > 1){
gap = gap / 3 + 1;
for(int i = 0; i < arr.length - gap; ++i){
int end = i;
//间隔为gap的为同一组数据
int key = arr[end + gap];
while(end >= 0 &&arr[end] > key){
arr[end + gap] = arr[end];
end -= gap;
}
arr[end + gap] = key;
}
}
}
3. 时间空间复杂度
3.1 时间复杂度
希尔排序的时间复杂度分析比较困难,这里只给出结论。
- 最好时间复杂度:O(n^1.3)
- 最坏时间复杂度:O(n^2)
3.2 空间复杂度
希尔排序中占用的空间为常数个空间,所以其空间复杂度为O(1)。