一,希爾排序算法介紹
①希爾排序又稱縮小增量排序 ,它本質上是一個插入排序算法。為什么呢?
因為,對於插入排序而言,插入排序是將當前待排序的元素與前面所有的元素比較,而希爾排序是將當前元素與前面增量位置上的元素進行比較,然后,再將該元素插入到合適位置。當一趟希爾排序完成后,處於增量位置上的元素是有序的。
②希爾排序算法的效率依賴於增量的選取
假設增量序列為 h(1),h(2).....h(k),其中h(1)必須為1,且h(1)<h(2)<...h(k) 。
第一趟排序時在增量為h(k)的各個元素上進行比較;
第二趟排序在增量為h(k-1)的各個元素上進行比較;
..........
最后一趟在增量h(1)上進行比較。由此可以看出,每進行一趟排序,增量是一個不斷減少的過程,因此稱之為縮小增量。
當增量減少到h(1)=1時,這里完全就是插入排序了,而在此時,整個元素經過前面的 k-1 趟排序后,已經變得基本有序了,而我們知道,對於插入排序而言,當待排序的數組基本有序時,插入排序的效率是非常高的。因此,希爾排序就是利用“增量”技巧將插入排序的平均時間復雜度O(N^2)降低為亞二次方。
二,希爾排序實例分析
假設原始數組為: 81 94 11 96 12 35 增量序列為 h(1)=1 h(2)=3
這里一共有兩個增量序列,故一共有兩趟排序。第一趟按照增量3來排序
處於增量3上的元素集合如下:<81 96>,<94 12>,<11 35>排序之后變成:<81 96>,<12 94>,<11 35>
即,數組變成了:81 12 11 96 94 35
可以看出,在增量為3的各個位置上的元素都是有序的。
經過前面一趟排序,此時數組已經基本有序,再使用增量為1的排序時(插入排序),比較的次數將會大大地降低。
第二趟按增量為h(1)=1 來排序,排序后數組變成:
11 12 35 81 94 96
三,算法實現
1 public class ShellSort { 2 3 /** 4 * 使用希爾推薦的增量: h(k)=N/2 , h(i)=h(i+1)/2 5 * @param arr 待排序數組 6 */ 7 public static <T extends Comparable<? super T>> void shellSort(T[] arr){ 8 9 int j; 10 for(int gap = arr.length; gap >= 1; gap /=2)//排序的趟數 11 { 12 13 /*每對增量序列為: 14 * <gap, 0> <gap+1,1> <gap+2,2>.....<arr.length-1, arr.length-1-gap> 15 */ 16 for(int i = gap; i < arr.length; i++)//在"增量位置上" 進行插入排序 17 { 18 T tmp = arr[i]; 19 for(j = i; j >= gap && tmp.compareTo(arr[j - gap]) < 0; j-= gap) 20 arr[j] = arr[j - gap]; 21 arr[j] = tmp; 22 } 23 } 24 } 25 26 //for test purpose 27 public static void main(String[] args) { 28 Integer[] arr = {81,94,11,96,12,35,17,95,28,58,41,75,15}; 29 shellSort(arr); 30 for (Integer integer : arr) { 31 System.out.print(integer + " "); 32 } 33 } 34 }
①第10行for循環表示,一共有多少個增量。增量的序列的個數,就是希爾排序的趟數。
上面的增量序列為: arr.length/2 , arr.length/2/2, arr.length/2/2/2 .... 2, 1
②里層的兩個for循環(第16行至23行)實際上是一個插入排序
③第16行for循環表示:從數組的第 arr.length/2 下標起,對各組增量序列上的元素進行插入排序。gap等於arr.length/2時的排序分析如下:
對於第一趟排序而言(gap等於arr.length/2),一共有多少組呢?答案是一共有 arr.length - arr.length/2 + 1 組。
即:arr[arr.length/2] arr[0] 這兩個元素是一組
arr[arr.length/2 + 1] arr[1] 這兩個元素是一組
.....
arr[arr.length -1] arr[arr.length-1-gap]這兩個元素是一組,此時gap等於arr.length/2
④第19行for循環,就是插入排序中的將當前元素與前面的元素進行比較。這里的前面元素是相差 gap 個位置上的元素。
比如, arr[length/2] 與 arr[length/2-gap]進行比較。
四:參考資料
至此,五大基於內存的排序算法:插入排序、希爾排序;歸並排序、快速排序;堆排序 全部已經實現了一遍。
插入排序和希爾排序是一類,本質上希爾排序就是插入排序。插入排序它的增量就是1,而希爾排序則按多個增量進行排序,增量逐漸減少至1
歸並排序和快速排序很像。之所以像,是因為它們都基於分治、遞歸。歸並排序每次將原數組遞歸分成兩個大小相等的子數組,而快速排序則是基於pivot元素將原數組分成兩個子數組。歸並排序不斷地分解原數組,直到划分的兩個子數組中只存在一個元素時,這時,這兩個子數組可以視為都是有序的。從而,再合並兩個有序的子數組,來實現排序。
堆排序,借助了二叉堆,邏輯上是一棵完全二叉樹,物理存儲結構是一個一維數組。通過不斷地刪除堆頂元素,來實現排序。
完
