由“插入排序”可知,它是一种不稳定的算法,当序列已经接近有序时,排序速度较快,但是如果最小的在最后面,那可能需要最小的交换了n-1次才能换到最前面。
希尔排序在插入排序的基础上进行了改进,交换不相邻的元素,以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序,具体做法如下。
1.假设数组长度为N,先以较大的增量gap将N进行划分几组,例如数组为{2,4,8,1,9,10,5,6,7,3}(N=10),增量gap=7,此时分组情况为{2,4,8,1,9,10,5},{6,7,3}
2.然后将每组的对应位置的数进行比较,小的交换到前面。2比6小不动,4比7小不动,3比8小,换前面,这一轮回下来的结果{2,4,3,1,9,10,5,6,7,8}
3.再将gap减小,重新分组,例如gap=4,分组情况为{2,4,3,1},{9,10,5,6},{7,8}
4.再将对应位置比较,交换,2<7<9,所以各自交换为{2,4,3,1,7,10,5,6,9,8},4<8<10,所以交换后的结果{2,4,3,1,7,8,5,6,9,10}
5.在减小gap,直到增量为1时,还是按照插入排序的算法,将顺序排完。{1,2,3,4,5,6,7,8,9,10}
具体算法如下
public static <T> void shellSort(Comparable<T>[] a){// 将a[] 升序排列 int N = a.length; int h = 1; while(h < N/3) h = 3 * h + 1;//1,4,13,40,121,...h为每次第二组的第一个元素的坐标,这样先两i=h后i++也就是第二组的后面的一直在和第一组的进行比较 while(h >= 1){//将数组变为h有序 for(int i = h; i < N; i++){//将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]...中 //j>=h目的是a[j-h]有意义,j -= h,是为了让a[i]和a[i -h],a[i -2*h],...比较 for(int j = i; j >= h&& less(a[j], a[j-h]); j -= h){ exch(a,j,j-h); } } //逐步减小h,直至h=1时结束 h = h / 3; } }
public class ShellSort { public static <T> void shellSort(Comparable<T>[] a){ // 将a[] 升序排列 int N = a.length; int h = 1; while(h < N/3) h = 3 * h + 1;//1,4,13,40,121,... 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&& less(a[j], a[j-h]); j -= h){ exch(a,j,j-h); } } h = h / 3; } } private static <T>boolean less(Comparable<T> v, Comparable<T> w){ return v.compareTo((T) w) < 0; } private static <T> void exch(Comparable<T>[] a, int i, int j){ Comparable<T> t = a[i]; a[i] = a[j]; a[j] = t; } private static <T> void show(Comparable<T>[] a){ //在单行中打印数组 for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } } public static <T> boolean isSorted(Comparable<T>[] a){ //测试数组元素是否有序 for (int i = 0; i < a.length; i++) { if(less(a[i], a[i-1])) return false; } return true; } public static void main(String[] args) throws FileNotFoundException { // TODO Auto-generated method stub // String[] a = {"S","O","R","T","E","X","A","M","P","L","E"}; Integer[] a = {-5,8,9,5,7,1,-68,1,5,33,100,563,526,16,21,68,-88}; for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } System.out.println(); shellSort(a); assert isSorted(a); show(a); } }
希尔排序需要比较的次数为 N^(4/3),或N^(5/4)...,它的性能提高了20%~40%
书上说:“有经验的程序员有时会选择希尔排序,因为对于中等大小的数组它的运行时间是可以接受的,它的代码量很小,且不需要使用额外的内存空间,如果需要解决一个排序问题而又没有系统排序函数可用,可以先用希尔排序,然后再考虑是否值得将它替换为更加复杂的排序算法。