概要
希尔排序(Shell Sort)是插入排序的一种改进版本,它的思想是把序列下标按一个增量进行分组,分别对每组使用直接插入排序算法排序,而后减小增量的值,再次进行分组和使用直接插入排序算法排序,沿用这个套路直至增量减小为1,这时整个序列被分为一组,算法变成了直接插入排序。时间复杂度O()。
直接插入排序算法存在的问题:若一个元素距离它在本该在的位置(排序后的位置)很远,它需要一步一步的移动到它该在的位置,例如:序列中最小的元素是最后一位,那么它需要和序列中所有其它元素对比并交换位置才能移动到最前面。希尔排序就是基于这一点对直接插入排序算法进行改进,希尔排序先用大增量h对序列分组排序,使元素可以一次移动h的步长,再减小增量h,使元素更接近它正确的位置,最后增量为1时序列已经接近有序,这时的直接插入排序是十分高效的。
例子:
初始待排序序列:9 7 3 8 5 6 4 1 2,下标为 i
第1趟排序增量h=4:起始待排序列:9 7 3 8 5 6 4 1 2
i%h = 0的为一组:9 7 3 8 5 6 4 1 2 ,排序后:2 7 3 8 5 6 4 1 9
i%h = 1的为一组:2 7 3 8 5 6 4 1 9 ,排序后:2 6 3 8 5 7 4 1 9
i%h = 2的为一组:2 7 3 8 5 6 4 1 9 ,排序后:2 7 3 8 5 6 4 1 9
i%h = 3的为一组:2 7 3 8 5 6 4 1 9 ,排序后:2 7 3 1 5 6 4 8 9
第2趟排序增量h=2:起始待排序列:2 7 3 1 5 6 4 8 9i%h = 0的为一组:2 7 3 1 5 6 4 8 9 ,排序后:2 7 3 1 4 6 5 8 9
i%h = 1的为一组:2 7 3 1 4 6 5 8 9 ,排序后:2 1 3 6 4 7 5 8 9
第3趟排序增量h=1:起始待排序列:2 1 3 6 4 7 5 8 9
这时算法变成了直接插入排序,对比一下待排序列:每个元素距离它正确的位置都很近,这使得直接插入排序十分高效。
2 1 3 6 4 7 5 8 9
1 2 3 4 5 6 7 8 9
而最初的情况是这样的:1 2 9 7 8这些元素距离它正确位置都非常远,这种差距会随着数据量的增大而变得更明显。
9 7 3 8 5 6 4 1 2
1 2 3 4 5 6 7 8 9
增量序列:希尔排序的时间复杂度依赖于增量序列,增量序列的最后一项必须为1,一个好的应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况,上面的例子就是一个反例,增量h=4和增量h=2的分组情况:2 7 3 8 5 6 4 1 9 和 2 7 3 1 5 6 4 8 9,其中2 5 9在h=4时分为一组,h=2时又在同一组,这是不好的,因为它们在经历过一次排序后就已经是有序的了。
Java实现代码
定义了一个排序接口,后面可用其它算法实现。
public interface Sort {
void sort(int[] array);
//交换数组i和j位置的值
default void exchange(int[] array, int i, int j){
int item = array[i];
array[i] = array[j];
array[j] = item;
}
}
插入排序的实现类
/**
* 希尔排序
*/
public class ShellSort implements Sort {
@Override
public void sort(int[] array) {
int h = 1;
while (h < array.length / 3) h = 3 * h + 1;//获得一个不大于array.length增量
while (h >= 1) {
for (int i = h; i < array.length; i++) {
// 将array[i]插入到array[i-h], array[i-2*h], array[i-3*h]... 之中
for (int j = i; j >= h; j = j-h) {
if (array[j] < array[j - h]) {
exchange(array, j, j - h);
}
}
}
h = h / 3;//每次增减变为原来的三分之一
}
}
public static void main(String[] args) {
ShellSort shellSort = new ShellSort();
TestUtil.test(1000, shellSort);
TestUtil.test(10000, shellSort);
TestUtil.test(100000, shellSort);
TestUtil.test(1000000, shellSort);
}
}
测试工具类,可生成测试数据和执行排序算法
public class TestUtil {
/**
* 返回一个大小为n的,由1到n之间的随机整数组成的数组
*/
public static int[] getRandomArray(int n){
return new Random().ints(n,1,n).toArray();
}
public void show(int array[]){
System.out.println(Arrays.toString(array));
}
public static void test(int n,Sort sort) {
int [] array = getRandomArray(n);
//show(array);
long startTime = System.currentTimeMillis();
sort.sort(array);
long endTime = System.currentTimeMillis();
//show(array);
System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
}
}
性能测试
分别测试了10^3,10^4,10^5,10^6数量级的排序时间
10^3:程序运行时间:4ms
10^4:程序运行时间:32ms
10^5:程序运行时间:2448ms10^6:程序运行时间:146454ms
应用场景
希尔排序算法实现简单,比插入排序和选择排序更快,在中等规模的数据排序中也有用武之地,对于更大规模的数据还有比希尔排序更高效的算法。