經典排序演算法——希爾排序
注:本文參考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
&n