希爾排序(Shell Sort)是插入排序的一種,是針對直接插入排序算法的改進,是將整個無序列分割成若干小的子序列分別進行插入排序,希爾排序並不穩定。該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。java
1、基本思想算法
先取一個小於n的整數d1做爲第一個增量,把文件的所有記錄分紅d1個組。全部距離爲d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;而後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即全部記錄放在同一組中進行直接插入排序爲止。shell
2、下面讓咱們看一下希爾排序的一個實例:數組
3、希爾排序的時間性能優於直接插入排序的緣由:
①當文件初態基本有序時直接插入排序所需的比較和移動次數均較少。
②當n值較小時,n和n2的差異也較小,即直接插入排序的最好時間複雜度O(n)和最壞時間複雜度0(n2)差異不大。
③在希爾排序開始時增量較大,分組較多,每組的記錄數目少,故各組內直接插入較快,後來增量di逐漸縮小,分組數逐漸減小,而各組的記錄數目逐漸增多,但因爲已經按di-1做爲距離排過序,使文件較接近於有序狀態,因此新的一趟排序過程也較快。
所以,希爾排序在效率上較直接插人排序有較大的改進。 性能
增量序列的選擇:Shell排序的執行時間依賴於增量序列。好的增量序列的共同特徵(查到的資料都這麼講): spa
① 最後一個增量必須爲1;
② 應該儘可能避免序列中的值(尤爲是相鄰的值)互爲倍數的狀況。 blog
4、具體的java代碼實現排序
public static void shellSort(int[] a){ double gap = a.length;//增量長度 int dk,sentinel,k; while(true){ gap = (int)Math.ceil(gap/2);//逐漸減少增量長度 dk = (int)gap;//肯定增量長度 for(int i=0;i<dk;i++){ //用增量將序列分割,分別進行直接插入排序。隨着增量變小爲1,最後總體進行直接插入排序 for(int j=i+dk;j<a.length;j = j+dk){ k = j-dk; sentinel = a[j]; while(k>=0 && sentinel<a[k]){ a[k+dk] = a[k]; k = k-dk; } a[k+dk] = sentinel; } } //當dk爲1的時候,總體進行直接插入排序 if(dk==1){ break; } } }
5、算法分析ci
排序類別table |
排序方法 |
時間複雜度 |
空間複雜度 |
穩定性 |
複雜性 |
||
平均狀況 |
最壞狀況 |
最好狀況 |
|||||
插入排序 |
希爾排序 |
O(Nlog2N) |
O(N1.5) |
|
O(1) |
不穩定 |
較複雜 |
步長的選擇是希爾排序的重要部分。只要最終步長爲1任何步長序列均可以工做。
算法最開始以必定的步長進行排序。而後會繼續以必定步長進行排序,最終算法以步長爲1進行排序。當步長爲1時,算法變爲插入排序,這就保證了數據必定會被排序。
Donald Shell 最初建議步長選擇爲N/2而且對步長取半直到步長達到1。雖然這樣取能夠比O(N2)類的算法(插入排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。可能希爾排序最重要的地方在於當用較小步長排序後,之前用的較大步長仍然是有序的。好比,若是一個數列以步長5進行了排序而後再以步長3進行排序,那麼該數列不只是以步長3有序,並且是以步長5有序。若是不是這樣,那麼算法在迭代過程當中會打亂之前的順序,那就
不會以如此短的時間完成排序了。
已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項來自這兩個算式。
這項研究也代表「比較在希爾排序中是最主要的操做,而不是交換。」用這樣步長序列的希爾排序比插入排序和堆排序都要快,甚至在小數組中比快速排序還快,可是在涉及大量數據時希爾排序仍是比快速排序慢。
算法穩定性
由上文的希爾排序算法演示圖便可知,希爾排序中相等數據可能會交換位置,因此希爾排序是不穩定的算法。
6、問題
希爾排序必定正確麼?換句話說如何選取增量序列才能保證正確(包括長度、值)?是的,最後一次只要保證增量是1就ok(無論序列長度,只不過效率就低了),如果序列只有1,那就是直接插入排序了,不知道對否。
7、下面給你們列出8種排序之間的關係: