希爾排序實現與複雜度、穩定性分析
import java.util.*; public class ShellSort { public int[] shellSort(int[] a, int n) { //先判斷條件 if(a== null|| n<2){ return a; } int feet = n/2; int index =0; while(feet > 0){ for(int i=feet;i<n;i++){ index = i; while(index >= feet){ if(a[index] <a[index-feet]){ swap(a,index,index-feet); index-=feet; }else{ break; } } } feet = feet/2; } return a; } public void swap(int[] a,int big,int little){ int temp = a[little]; a[little] = a[big]; a[big] = temp; } }
希爾排序(Shell Sort)又叫做縮小增量排序(diminishing increment sort),是一種很優秀的排序法,演算法本身不難理解,也很容易實現,而且它的速度很快。
Shell排序通過將資料分成不同的組,先對每一組進行排序,然後再對所有的元素進行一次插入排序,以減少資料交換和移動的次數。
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入排序對於有序的序列效率很高。
由於多次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,所以,Shell排序是不穩定的。
插入排序(Insertion Sort)的一個重要的特點是,如果原始資料的大部分元素已經排序,那麼插入排序的速度很快(因為需要移動的元素很少)。從這個事實我們可以想到,如果原始資料只有很少元素,那麼排序的速度也很快。--希爾排序就是基於這兩點對插入排序作出了改進。
例如,有100個整數需要排序。
- 第一趟排序先把它分成50組,每組2個整數,分別排序。
- 第二趟排序再把經過第一趟排序後的100個整數分成25組,每組4個整數,分別排序。
- 第三趟排序再把前一次排序後的數分成12組,第組8個整數,分別排序。
- 照這樣子分下去,最後一趟分成100組,每組一個整數,這就相當於一次插入排序。
由於開始時每組只有很少整數,所以排序很快。之後每組含有的整數越來越多,但是由於這些數也越來越有序,所以排序速度也很快。
希爾排序平均效率是O(nlogn)。其中分組的合理性會對演算法產生重要的影響。現在多用D.E.Knuth的分組方法。下面來分析原始的希爾排序的時間複雜度,初始步長d=n/2,下一次步長d=d/2
第一次比較次數,每組2個元素:1*n/2
第二次比較次數,每組4個元素:最壞(1+2+3)*n/4
第三次比較次數,每組8個元素:最壞(1+2+3+……+7)*n/8
... ...
2^(m-1) ( m 表示第幾次比較 ) < 每組最壞的元素比較次數 < 2^(m)
例子 :2^2 < 7 < 2^3 (第 3 次比較,最後一個元素的最差比較次數 )
累加求極限,得到演算法複雜度小於 O(n^2) 。
詳見:http://blog.csdn.NET/ginnosx/article/details/12263619
Shell排序比氣泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相對比較簡單,它適合於資料量在5000以下並且速度並不是特別重要的場合。它對於資料量較小的數列重複排序是非常好的。
由於開始時每組只有很少整數,所以排序很快。之後每組含有的整數越來越多,但是由於這些數也越來越有序,所以排序速度也很快。
然而,情況並不總是這麼理想的,在一些特定(但並不算罕見)的情況下,雖然經過了很多趟排序但是資料卻沒有變得更有序。例如,如果用上面的演算法對下面這些數進行排序
1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 8, 16
會得到以下結果:
after gap(8): 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 8, 16
after gap(4): 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 8, 16
after gap(2): 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 8, 16
after gap(1): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
在 gap=1 之前的每一趟排序都在浪費時間!
這種壞情形是可以避免的,就是把上面的增量數列(1, 2, 4, 8)改成Hibbard增量(1, 3, 5, 7)。
由此可見,增量數列的選擇對希爾排序的效能有著極大的影響。[Mark Allen Weiss]指出,最好的增量序列是 Sedgewick提出的 (1, 5, 19, 41, 109,...),該序列的項來自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 這兩個算式。