希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
代码思路与演示
一、交换式希尔排序
1.设定步长,也就是最外层循环,每次循环gap减半
2.设置外层循环,可假设为一个后向指针,执行 n-gap 次,向后遍历
3.设置内层循环
3.1 让指定元素与本组前面的元素做比较[ arr(i) vs arr(i-gap)],有逆序就交换!
3.2 这里类似一个前向指针!每次都要不断前向遍历直到本组的首位,像是冒泡排序
4.当gap减到1,执行完前面步骤后,整个序列就是有序的了
//交换式的希尔排序(相当于先分组然后使用冒泡,效率较低)
public static Integer[] ShellSort1(Integer[] arr) {
//每次排序步长减半(步长是几就相当于分了几组,n/stride是每组元素的个数)
for (int stride = 50; stride > 0; stride /= 2) {
//相当于指针,从前向后取元素,正好执行 n-stride 个大循环
for (int i = stride; i < arr.length; i++) {
//用于比较同一组的元素(两两比较),有逆序就交换,还要跟冒泡一样从后往前让它前面同组的所有相邻元素互相比较,从而实现每组都是有序的。
for (int j = i - stride; j >= 0; j -= stride) {
if (arr[j] > arr[j + stride]) {
Integer temp = arr[j];
arr[j] = arr[j + stride];
arr[j + stride] = temp;
}
}
}
}
return arr;
}
二、移位式希尔排序(感觉这个才是正统的)
1.设定步长,也就是最外层循环,每次循环gap减半
2.设置外层循环,可假设为一个后向指针,执行 n-gap 次,从前往后遍历
3.设置内层循环
3.1 这里要先设置两个辅助变量,
1)保存待插值,就是 2 中指针指向的值
2)待插索引,初始指向待插值前一位(注意有gap)
3.2 让待插值与本组前面的元素分别做比较,有逆序就让该元素后挪一位(注意gap)!
3.3 直到到达首位或者遇到比待插值小的元素,就插入到它的后面一位(同样注意gap)
4.当gap减到1,执行完前面步骤后,整个序列就是有序的了
//移位式的希尔排序(相当于先分组然后使用插入排序,效率较高)
public static Integer[] ShellSort2(Integer[] arr) {
//每次排序步长减半(步长是几就相当于分了几组,n/stride是每组元素的个数)
for (int stride = 50; stride > 0; stride /= 2) {
//相当于指针,从前向后取元素,正好执行 n-stride 个大循环
for (int i = stride; i < arr.length; i++) {
Integer insertVal = arr[i];//取出待插值
Integer insertIndex = i-stride;//待插位置
//如果某元素比本组的上一个元素小,就执行后移位操作重复向前遍历直到遇到比待插元素小的元素
while (insertIndex - stride >= 0 && insertVal < arr[insertIndex - stride]) {
arr[insertIndex] = arr[insertIndex - stride];
insertIndex -= stride;
}
//待插元素插入到该元素的后面一位(注意步长)
arr[insertIndex + stride] = insertVal;
}
}
return arr;
}
测试与速度对比
@Test
public void testShellSort() {
Integer[] arr = init();
System.out.println("原始数据为:");
print(arr);
Long beginTime = System.currentTimeMillis();
Integer[] result = ShellSort1(arr);
Long endTime = System.currentTimeMillis();
System.out.println("交换式排序结果为:");
print(result);
System.out.println("耗时:" + (endTime - beginTime) + " ms");
System.out.println();
beginTime = System.currentTimeMillis();
result=ShellSort2(arr);
System.out.println("移位式排序结果为:");
print(result);
endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime - beginTime) + " ms");
}
总结
上面提到的两个方法可以理解为 分组+冒泡 、分组+插入 。很明显第二种方法是更高效的,也是更符合算法描述的。这个算法理解上简单,但实际实现时却感觉无从下手,尤其是第一种方法的代码,能看懂就不容易,更不要说自己敲了!