由“插入排序”可知,它是一种不稳定的算法,当序列已经接近有序时,排序速度较快,但是如果最小的在最后面,那可能需要最小的交换了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%
书上说:“有经验的程序员有时会选择希尔排序,因为对于中等大小的数组它的运行时间是可以接受的,它的代码量很小,且不需要使用额外的内存空间,如果需要解决一个排序问题而又没有系统排序函数可用,可以先用希尔排序,然后再考虑是否值得将它替换为更加复杂的排序算法。