排序演算法之希爾排序
演算法分析:
希爾排序(Shell Sort)是插入排序的一種,其實質就是分組插入排序,該方法又稱縮小增量排序,因D.L.Shell於1959年提出而得名。它是對直接插入排序的一種改進,通過加大插入排序中元素之間的間隔,並在這些有間隔的元素中進行插入排序,從而使得資料項大跨度的移動。
基本思想:
先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。
假設當前增量h為4(間隔),對[9,-16,21,23,-30,-49,21,30,30]進行shell排序:
①[9,-16,21,23,-30,-49,21,30,30]—>[-30,-16,21,23,9,-49,21,30,30]
②[-30,-16,21,23,9,-49,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
③[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
④[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
⑤[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
⑥[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
第一趟排序後只需要保證間隔為4的元素之間有序即可。從上面我們發現⑤⑥兩步存在這樣一個關係,第⑤的結果有可能造成之前判斷過的元素再次無序(如果發生元素移動,沒有發生的話將不會造成),需要進行第⑥的判斷,所以我們在移動元素的時候應該使用一個迴圈來進行移動。
下一趟的判斷時需要對增量進行遞減處理;
從上面的分析可以看出,shell排序的關鍵在於增量序列的值,常用的增量序列由Knuth提出,該序列從1開始,由如下公式產生:h = h*3 + 1
程式中要反向計算增量序列:h = (h-1)/3
java程式碼實現:
package fanzhenhua.sort;
import java.util.Arrays;
public class ShellSort {
public static void shellSort(DataWrap[] data){
//希爾排序:根據增量進行直接插入排序,依次遞減增量,直到增量為<1為止
int length=data.length;
//儲存增量的基值
int h=1;
//這裡的目的是求出增量的最大值
while(h<length/3){
h=h*3+1;
}
DataWrap temp=null;
while(h>0){
System.out.println("h======= "+h);
//從增量的起始位置開始判斷起
for(int i=h;i<length;i++){
//儲存待判斷的元素
temp=data[i];
//如果待判斷的元素小於增量之前的元素,則說明該增量所在的元素必須移動
if(data[i].compareTo(data[i-h])<0){
//獲得比增量所在元素大的元素的位置
int j=i-h;
//迴圈移動,因為可能出現移動後的元素導致之前的增量間隔元素之間變成無序
//當然,如果沒有存在移動現象的話,那麼重複判斷的時候不會對結果造成影響
for(;j>=0&&data[j].compareTo(temp)>0;j-=h){
//j+h為待判斷元素的位置,該位置被較大元素覆蓋。
data[j+h]=data[j];
}
//移動結束後,儲存待判斷的元素,位置為j+h(之所以需要+h的原因是因為上一個迴圈的結尾已經-h了)
data[j+h]=temp;
}
System.out.println(Arrays.toString(data));
}
//依次減小增量的值
h=(h-1)/3;
}
}
public static void main(String[] args) {
DataWrap[] data = {
new DataWrap(9, "")
,new DataWrap(-16, "")
,new DataWrap(21, "")
,new DataWrap(23, "*")
,new DataWrap(-30, "")
,new DataWrap(-49, "")
,new DataWrap(21, "")
,new DataWrap(30, "")
,new DataWrap(3, "")
,new DataWrap(67, "")
,new DataWrap(35, "")
,new DataWrap(5, "")
,new DataWrap(7, "")
,new DataWrap(15, "")
,new DataWrap(35, "")
};
System.out.println("排序之前:" + Arrays.toString(data));
shellSort(data);
System.out.println("排序之後:" + Arrays.toString(data));
}
}
執行結果:
排序之前:[9, -16, 21, 23*, -30, -49, 21, 30, 3, 67, 35, 5, 7, 15, 35] h======= 13 [9, -16, 21, 23*, -30, -49, 21, 30, 3, 67, 35, 5, 7, 15, 35] [9, -16, 21, 23*, -30, -49, 21, 30, 3, 67, 35, 5, 7, 15, 35] h======= 4 [-30, -16, 21, 23*, 9, -49, 21, 30, 3, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 9, -16, 21, 30, 3, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 9, -16, 21, 30, 3, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 9, -16, 21, 30, 3, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 3, -16, 21, 30, 9, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 3, -16, 21, 30, 9, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 23*, 3, -16, 21, 30, 9, 67, 35, 5, 7, 15, 35] [-30, -49, 21, 5, 3, -16, 21, 23*, 9, 67, 35, 30, 7, 15, 35] [-30, -49, 21, 5, 3, -16, 21, 23*, 7, 67, 35, 30, 9, 15, 35] [-30, -49, 21, 5, 3, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-30, -49, 21, 5, 3, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] h======= 1 [-49, -30, 21, 5, 3, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, 21, 5, 3, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, 5, 21, 3, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, 3, 5, 21, -16, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 21, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 21, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 21, 21, 23*, 7, 15, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 7, 21, 21, 23*, 15, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 7, 15, 21, 21, 23*, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 7, 15, 21, 21, 23*, 35, 30, 9, 67, 35] [-49, -30, -16, 3, 5, 7, 15, 21, 21, 23*, 30, 35, 9, 67, 35] [-49, -30, -16, 3, 5, 7, 9, 15, 21, 21, 23*, 30, 35, 67, 35] [-49, -30, -16, 3, 5, 7, 9, 15, 21, 21, 23*, 30, 35, 67, 35] [-49, -30, -16, 3, 5, 7, 9, 15, 21, 21, 23*, 30, 35, 35, 67] 排序之後:[-49, -30, -16, 3, 5, 7, 9, 15, 21, 21, 23*, 30, 35, 35, 67]
希爾排序演算法分析:
1.增量序列的選擇 Shell排序的執行時間依賴於增量序列。 好的增量序列的共同特徵: ① 最後一個增量必須為1; ② 應該儘量避免序列中的值(尤其是相鄰的值)互為倍數的情況。 有人通過大量的實驗,給出了目前較好的結果:當n較大時,比較和移動的次數約在nl.25到1.6n1.25之間。 2.Shell排序的時間效能優於直接插入排序 希爾排序的時間效能優於直接插入排序的原因: ①當檔案初態基本有序時直接插入排序所需的比較和移動次數均較少。 ②當n值較小時,n和n2的差別也較小,即直接插入排序的最好時間複雜度O(n)和最壞時間複雜度0(n2)差別不大。 ③在希爾排序開始時增量較大,分組較多,每組的記錄數目少,故各組內直接插入較快,後來增量di逐漸縮小,分組數逐漸減少,而各組的記錄數目逐漸增多,但由於已經按di-1作為距離排過序,使檔案較接近於有序狀態,所以新的一趟排序過程也較快。 因此,希爾排序在效率上較直接插人排序有較大的改進。 3.穩定性 希爾排序是不穩定的。參見上述例項,該例中兩個相同關鍵字49在排序前後的相對次序發生了變化。
參考文章: