希尔排序的实现思想
希尔排序是插入排序的改进版,它与插入排序的不同之处在于,它会优先比较距离较远的元素。因此希尔排序又叫缩小增量排序。
- 选择一个增量序列,一般好的增量序列都有两个共同的特征:
- 最后一个增量必须为1,保证最后一趟试一次普通的插入排序;
- 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
本文中使用增量序列h = 3 * h + 1
来生成。h初值被赋予1,然后使用该公式生成序列1,4,13,40,121,364等等,当间隔大于数组大小的时候停止,使用序列的最大数组作为间隔开始希尔排序过程,然后每完成一次排序,用倒推公式h = h / 3
来减小间隔,保证最后一次h = 1,完成最后一次插入排序。 - 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
- 仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
Java实现希尔排序
- 常规实现方法
public static void shellSort(int[] a) {
int h =1;//希尔排序是使间隔为h的元素有序
int len = a.length;
int temp,j;
while(h < len / 3) {//生成最大h
h = 3*h + 1;//用三倍作为希尔排序的间隔,是常用的值,加一是为了防止排序的都是三的倍数
}
while(h >= 1) {//while循环让h从大到小插入排序
for(int i = h;i < len;i++) {//外层循环,从第h+1个元素开始,遍历后面的每一个元素,i为要比较的元素
temp = a[i];//将要比较的元素存储在temp中。
for(j = i-h;j >= 0 && a[j] > temp;j -= h) {//遍历把i每隔h位进行比较,确定要插入的位置
a[j+h] = a[j];//当前面的元素比temp大时,将前面的值放在后面
}
a[j+h] = temp;//找到了要插入的位置,把要比较的值赋值给相应的元素,因为循环中j-h了,相当于a[i]。
}
h = h/3;//缩小插入间隔
}
}
- 每次比较,如果前面的更大都会交换,可以优化一下,直接把上面插入算法潜入内循环,比较的间隔由1改为h,比较的时候只移动插入位置,比较完只需要交换一次java实现。
public static void shellSort(int[] a) {
int h = 1; //希尔排序是使间隔为h的元素有序
int insertIndex,insertElement;
while(h < a.length/3) { //while循环,扩大h
h = 3*h + 1; //这里用3倍作为希尔排序的间隔,是常用的值,加1是为了防止排序的都是3的倍数
}
while(h >= 1){ //while循环让h从大到小插入排序
for(int i = h; i < a.length; i++){ //从第h+1个元素开始,对整个数组遍历,i为要比较元素的位置
insertIndex = i - h; //要比较元素的位置,默认前面间隔h的位置
insertElement = a[i]; //要比较的的元素,默认外层循环的最后一个元素
while(insertIndex >= 0 && a[insertIndex] > insertElement){ //内层循环,只要新元素比要比较位置的元素小就继续
a[insertIndex + h] = a[insertIndex]; //比要比较元素大的元素后移h位
insertIndex -= h; //插入位置前移h位
}
a[insertIndex + h] = insertElement; //内层循环结束,把新元素放到插入位置后面
}
h = h/3; //更大间隔的插入完成,缩小插入间隔
}
}
总结
希尔排序时间复杂度为O(NlogN),最好与最坏情况要根据具体的增量序列来判断,对于不同的增量序列有不同的复杂度。希尔排序的性能由于直接插入排序,因为在希尔排序开始时增量较大,分组较多,每组的记录数少,故各组内直接插入较快,后来随着增量逐渐减小,分组数逐渐减少,而各组的记录数逐渐增多,但是由于已经局部排过序了,所以已经接近有序状态,新的一趟排序过程也叫快。因此,希尔排序在效率上较直接插入排序有较大改进。
希尔排序是不稳定,因为不同的间隔对应的数据是独自比较的,如果a=b,但是不在同一个间隔上,显示会出现前后颠倒的情况,所以希尔排序是不稳定。
空间复杂度为O(1),不需要额外的存储空间。