注:本文參考https://www.cnblogs.com/chengxiao/p/6104371.html
希爾排序原理
在講解希爾排序之前,我們有必要先回頭看一下插入排序的問題。插入排序不管數組分布時怎么樣的,都是一步步的對元素進行比較,移動,插入。比如[5,4,3,2,1,0]這種倒序序列,數組末端的0要回到首位很費勁,比較和移動元素均需n-1次。這時就引出了希爾排序。
希爾排序是希爾(Donald Shell)於1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之后的更高效的版本。該算法是突破O(n^2)的第一批算法之一。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序。隨着增量逐漸減小,每組包含的數字越來越多,當增量減至1時,整個文件恰好被分成一組,算法便終止。
圖解希爾排序
我們來看下希爾排序的基本步驟,我們選擇增量gap = length/2,縮小增量繼續以gap = gap/2 的方式,這種增量選擇我們可以用一個序列來表示,{n/2,(n/2)/2...1}的增量序列。
原始數組 以下數據元素顏色相同的為一組
初始增量 gap = length/2 = 5,所以整個數組被分為5組,即從第一位置的數字開始向后+n*gap為同一組,所以第一次增量后5組為[8,3],[9,5],[1,4],[7,6],[2,0]
對這五組分別進行插入排序,例如[8,3]插入排序后會交換位置[3,8],[9,5]插入排序后會交換位置為[5,9]等等。所以執行后會將小的數字放在數組的前面。
然后繼續減小增量gap=5/2 = 2,數組被分為2組,即從第一位置的數字開始向后+n*gap為同一組,搜衣第二次增量后2組為[3,1,0,9,7] 和 [5,6,8,4,2]。如下圖所示
對以上兩組再分別進行插入排序,在[3,1,0,9,7]中會變為[0,1,3,7,9], [5,6,8,4,2]會變為[2,4,5,6,8],如下圖所示,可以看到整個數組的有序程度更進一步了。
再縮小增來gap=2/2 = 1 ,此時整個數組為1組[0,2,1,4,3,5,7,6,9,8],如下圖所示
經過上面的“宏觀調控”,整個數組已經基本有序。此時再對整個數組進行插入排序,只需進行少量的交換即可變為有序數組。
代碼實現
1.交換式實現方式
1 /** 2 * 交換式排序 3 * @param array 4 */ 5 public static void sort1(int[] array){ 6 int temp = 0; 7 int count = 0; 8 for(int gap=array.length/2; gap >0; gap /= 2){ 9 for (int i = gap; i < array.length; i++) { 10 int j = i - gap; 11 while (j >=0 && array[j] > array[j+gap]){ 12 //所在分組需做交換 13 temp = array[j]; 14 array[j] = array[j+gap]; 15 array[j+gap] = temp; 16 j -= gap; 17 } 18 } 19 } 20 }
2.移位式實現方式
1 /** 2 * 移位式排序 3 * @param array 4 */ 5 public static void sort2(int[] array){ 6 for(int gap = array.length/2; gap > 0; gap /= 2){ 7 for(int i=gap; i< array.length; i++) { 8 int insertValue = array[i]; 9 int insertIndex = i - gap; 10 int startIndex = insertIndex; 11 while (insertIndex >= 0 && insertValue < array[insertIndex]) { 12 //所在分組需做后移 13 array[insertIndex + gap] = array[insertIndex]; 14 insertIndex -= gap; 15 } 16 if(insertIndex != startIndex){ 17 array[insertIndex + gap] = insertValue; 18 } 19 } 20 } 21 }
代碼分析
1)第一層for循環用於確定分組大小,這里選用對數組大小減半的的方式,后面每次對原分組減半,直到分組大小為1
2)第二層for循環表示從第gap個元素開始(這里說明下為什么從第gap個元素開始:第gap個元素剛好為分組中的第一組的第二個元素,因為我們的插入排序為從第二個元素開始向前比較),向后依次執行插入排序,直到最后一個元素
3)while循環用於對每個分組進行插入排序(交換式為當找到比插入元素大的元素時,交換兩個元素。移位式為當找到比插入元素大時,將那個元素向后移動gap位,最后再將待插入元素放在空缺的位置即可)
時間復雜度
從我們的分析可知,希爾排序中對於增量序列的選擇十分重要,直接影響到希爾排序的性能。我們上面選擇的增量序列{n/2,(n/2)/2...1},其最壞時間復雜度依然位O(n^2),一些經過優化的增量序列如Hibbard經過復雜證明可使得最壞時間復雜度為O(n3/2)。
測試算法執行效率
與前面的排序算法相同,我們依然生成10萬個隨機數的數組,使用插入排序方法進行排序,看其執行時間。測試代碼如下
1 public static void main(String []args){ 2 int[] array = new int[1000000]; 3 for (int i = 0; i < 1000000; i++) { 4 array[i] = (int) (Math.random() * 8000000); 5 } 6 long begin = System.currentTimeMillis(); 7 sort2(array); 8 System.out.println("總耗時="+(System.currentTimeMillis()- begin)); 9 }
測試結果
可以看到希爾排序算法比插入排序更快,10萬個數據的數組排序大概需要300多毫秒時間。下篇我們將介紹快速排序算法,排序效率是否會更高呢?一起期待!
總結
希爾排序是一個基於插入排序的算法,解決了當待排序數組倒序時使用插入排序算法要移動多次的耗時操作。希爾排序的核心算法為先將待排序數組進行分組進行小范圍的排序,每次分組排序后待排序數組就會在大范圍上更有序。當分組大小減小到1時,待排序數組已經基本有序,此時再執行插入排序,可以確保移動或交換的次數更少,從而達到提升排序效率的目的。
注:本文參考https://www.cnblogs.com/chengxiao/p/6104371.html