在写希尔排序之前,首先我对之前的Java代码进行了优化。
之前的代码中由于Comparable<T>中的T都是用的String,因此在写一个测试方法时,必须对输入的数字进行转换,十分麻烦。 因此我将方法都写成了泛型的写法:
package com.jacob.demo; /** * This is a sample for sort algorithms. * @author jacob * */ public class Example { /** * The function for sort. * @param a */ public static<T> void sort(Comparable<T>[] a) { } /** * Compare if v is less than w. * @param v * @param w * @return */ public static<T> boolean less(Comparable<T> v, Comparable<T> w) { return v.compareTo((T) w) < 0; } /** * exchange two elements. * @param a * @param i * @param j */ public static<T> void exch(Comparable<T>[] a, int i, int j) { Comparable<T> t = a[i]; a[i] = a[j]; a[j] = t; } /** * Print array. * @param a */ private static<T> void show(Comparable<T>[] a) { for (int i= 0; i< a.length; i++) { System.out.print(a[i] + " "); } System.out.println(); } /** * Judge if array is sorted * @param a * @return */ public static<T> boolean isSorted(Comparable<T>[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i-1])) { return false; } } return true; } public static void main(String[] args) { String[] a = {"S","O","R","T","E","X","A","M","P","L","E"}; System.out.println("Before sort:"); show(a); System.out.println("sort begin:"); sort(a); assert isSorted(a); } }
之后的代码也会采用该模板。
言归正传。
1. 希尔排序的实现思路
希尔排序是基于插入排序改进出来的一种排序方法。 对于插入排序来说,当遇到大规模乱序数组的时候,速度是比较慢的,因为它只会交换相邻的元素,因此元素只能一个一个从数组一端移动到另一端,上一篇的例子中是从末尾移动到对应的位置。
希尔排序为了加快速度,改进了插入排序。它将数组根据间隔元素数h分成了几个小数组,然后对这些小数组进行局部插入排序,然后递减h,最终将这些全部排序。
例如:
Before sort:
S H E L L S O R T E X A M P L E //初始数组
sort begin:
//第一次分组,间隔h取13
[P] [H] [E] [L] L S O R T E X A [M] [S] [L] [E] //第一次排序结束
a loop is over; h is 13
// 第二次分组,h 取 4
[L] H E L [P] S O R [T] E X A [M] S L E // 对 P L T M 进行插入排序
[L] H E L [P] S O R [T] E X A [M] S L E
[L] H E L [P] S O R [T] E X A [M] S L E
[L] H E L [P] S O R [T] E X A [M] S L E
[L] H E L [P] S O R [T] E X A [M] S L E
L [E] E L P [H] O R T [S] X A M [S] L E // 对 H S E S 进行插入排序
L [E] E L P [H] O R T [S] X A M [S] L E
L [E] E A P [H] O L T [S] X R M [S] L E
L [E] E A M [H] O L P [S] X R T [S] L E
L E [E] A M H [O] L P S [X] R T S [L] E //对E O X L j进行插入排序
L E [E] A M H [L] L P S [O] R T S [X] E
L E E [A] M H E [P] S O L [T] S X R //对A P T 进行插入排序
// 第三次分组,h取1, 此时就是对数组进行一次插入排序
E L E A M H L E P S O L T S X R
E E L A M H L E P S O L T S X R
A E E L M H L E P S O L T S X R
A E E L M H L E P S O L T S X R
A E E H L M L E P S O L T S X R
A E E H L L M E P S O L T S X R
A E E E H L L M P S O L T S X R
A E E E H L L M P S O L T S X R
A E E E H L L M P S O L T S X R
A E E E H L L M O P S L T S X R
A E E E H L L L M O P S T S X R
A E E E H L L L M O P S T S X R
A E E E H L L L M O P S S T X R
A E E E H L L L M O P S S T X R
A E E E H L L L M O P R S S T X
//排序结束
根据上述的希尔排序的实现思路,实现代码如下:
/** * The function for sort. * @param a */ public static<T> void sort(Comparable<T>[] a) { int h = 1; int N = a.length; while(h < N/3) { h = 3*h + 1; } while(h >=1) { for(int i = h; i < N; i++) { for(int j= i; j >= h && less(a[j], a[j-h]); j-=h) { exch(a, j, j-h); } show(a); } System.out.println("a loop is over; h is " + h); h=h/3; } }
希尔排序更高效的原因是它权衡了子数组的规模和有序性。
另外,选择h也是一个难点,一般取决于其数学性质,如公因子等。
JS的实现
根据希尔排序的实现原理,JS的实现代码如下:
function shell(array) { let h = 1; // splite array to serveral arraies with h elements. while(h < array.length / 3) { h = 3*h +1; } // insertion for h elements array. while(h >= 1) { for(i = h; i < array.length; i++) { for(j = i; j >= h && array[j] < array[j-h]; j-=h ) { exch(array, j-h, j); } console.log(array); } console.log("a loop is done, h is " + h); h=parseInt(h/3); } } function exch(array, firstIndex, minIndex) { let temp = array[firstIndex]; array[firstIndex] = array[minIndex]; array[minIndex] = temp; } function main() { var arr = [5,3,7,1,7,8,1]; console.log(arr); shell(arr); console.log("after sort"); console.log(arr); } main();