希尔排序
1.定义:
(1)基于插入排序的排序算法
(2)原因:对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组的一端移动到另一端。例如:若值最小的元素正好在数组的尽头,要将它挪到正确的位置就要要进行 数组的长度-1 次移动。
(3)希尔排序时先将任意间隔为h的元素都进行插入排序,同时,间隔h是一个递增序列中的一个值,且h按照递增序列递减,即这个h会随着每一次循环排序结束后越来越小,因此,排序的精度也会越来越高。
(4)希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组称为h有序数组。一个h有序数组就是h个互相独立的有序数组编织在一起组成的一个数组。
(5)希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的,这两种情况都很适合插入排序。
(6)到现在为止,没有任何一个递增序列被发现是效率最高的,因此,透彻理解希尔排序的性能至今仍然是一个挑战。但我们还是知晓它的性能绝对是比一些初级排序高的。
(7)总结:博主认为希尔排序其实是对数组进行多次插入排序,前面的插入排序行为都是为了使得最后的那一次插入排序变的更加容易,即更适合插入排序。
2.代码剖析:
假设:数组a,长度N,下标 i,间隔为h,递增序列3 * n + 1,因此间隔h = 3 * n + 1(n=0,1,2,3... ; h=1,4,13,40,121,264...)。
(1)首先,我们要获得第一个间隔h,这个h既要在递增序列中,又要满足刚好大于N/3(这里的3是我们本文使用的递增序列的常数因子3)
(2)获得了第一个间隔h后,我们通过间隔h,将间隔为h的元素定为一组,分别进行插入排序。排序完成后,第一次循环结束。
(3)接着,在一次循环结束后,这时我们的间隔h应该要变的更小以使排序精确度增加。因此,可以直接h = h/3 就可以得出下一个间隔h,并用这个h再将数组分割成多个大小为h的数组,分别进行插入排序。
(4)如此以往,直到h = 1 时,进行最后一次排序,其实此时就相当于对数组进行插入排序(但由于前面已经对其做了处理,所以这次的插入排序速度那是相当的快滴)。
(5)注:为了使得h最后能够变成1,因此递增序列最后需要加个1来保证。
3.图解:
4.按照这个思路,写出代码:
/**
* 希尔排序
* 排序过程:
* 1.选择一个递增序列,本算法用的递增序列为 3*n+1
* 2.通过该递增序列,先获得最大的间隔h
* 3.数组将间隔为h的元素编为一组,组内进行插入排序
* 4.当所有组排序结束后,间隔h按照递增序列减小,获得新的间隔h
* 5.再用新的间隔h进行排序,直到最后间隔h=1的时候排序结束
*
* @param array
*/
//递增序列的常数因子
private static final int MULTIPLE = 3;
public static void shellSort(int[] array) {
//将a[]按升序排序
int N = array.length;
int h = 1;
int t;
//获得第一个间隔h
while (h < N / MULTIPLE) {
//这里加一是为了能够最后可以使得h变为1
h = MULTIPLE * h + 1;
}
while (h >= 1) {
//将数组变为h有序
for (int i = h; i < N; i++) {
//对a[i],a[i-h],a[i-2*h],a[i-3*h]..进行插入排序
for (int j = i; j >= h && array[j] < array[j - h]; j -= h) {
t = array[j];
array[j] = array[j - h];
array[j - h] = t;
}
}
//一次循环结束,更新间隔h
h = h / MULTIPLE;
}
}
5.算法优劣分析:
(1)毋庸置疑,效率肯定比插入排序高,并且数组越大,优势越大。
(2)递增序列的选择会影响希尔排序的效率,本文使用的递增序列应该可以对付大部分情况。
(3)对于中等大小的数组,使用希尔排序时可以接收的,它不仅代码量很小,而且不需要使用额外的内存空间。虽然有更加高效的其他算法,但除了数组非常非常打之外,它们的速度可能只会比希尔排序快两倍,关键是更复杂。
(4)因此,如果需要解决一个排序问题而又没有系统排序函数可用,可以先用希尔排序,然后再考虑是否应该将它替换为更加复杂的排序算法。
6.资料引用:
《算法Algorithms(第4版)》 p162~p165
上一节:Java算法(2):插入排序
下一节:Java算法(4):归并排序