Java資料結構與演算法筆記——希爾排序
技術標籤:資料結構與演算法
文章目錄
不同排序方式的優缺點
三種簡單的排序演算法冒泡、選擇、插入排序演算法,它們的時間複雜度大,都是O(N^2),如果資料量少,我們還能忍受,但是資料量大,那麼這三種簡單的排序所需要的時間有可能會是是我們所不能接受的。
在學習解遞迴的時候,介紹了歸併排序,歸併排序需要O(NlogN),這比簡單排序要快了很多,但是歸併排序有個缺點,它需要的空間是原始陣列空間的兩倍,當我們需要排序的資料佔據了整個記憶體的一半以上的空間,那麼是不能使用歸併排序的。
於是就有了兩種快速排序:希爾排序和快速排序。
希爾排序
演算法原理
直接插入排序存在一個效率問題,如果一個很小的數在很靠近右邊的位置,那麼想讓這個很小的數插入到左邊排好序的位置,那麼左邊排好序的資料項都必須向右移動一位,這個步驟就是將近執行了N次複製,雖然不是每個資料項都必須移動N個位置,但是每個資料項平均移動了N/2次,N個數據項總共就是N*N/2次,因此插入排序的效率是O(N^2)。
如果以某種方式不必一個一個移動中間所有的資料項,就能把較小的資料項移動到左邊,那麼這個演算法的執行效率會有很大的改進。
希爾排序通過加大插入排序中元素的間隔,並在這些有間隔的元素中進行插入排序,從而使資料項能夠大跨度的移動。當這些資料項排過一趟後,希爾排序減小資料項的間隔再進行排序,依次進行下去,最後間隔為1時,就是我們上面說的簡單的直接插入排序
排序間隔選取
希爾原稿中,他建議間隔選為N/2,也就是每一趟都講排序分為兩半。但是這已經被證明不是最好的序列。
另一種變形方法是使用2.2整除每一個間隔,這比用2整除會顯著改善排序效果。
還有一種很常用的間隔序列:在不小於len/3下間隔序列step*3+1,每次迴圈後間隔(step-1)/3。
無論是什麼間隔序列,最後必須滿足一個條件,就是逐漸減少間隔最後一定要等於1,因此最後一趟排序一定是簡單的插入排序。
希爾排序圖解
初始步長h為4:
比較兩個元素大小:
交換兩個元素位置:
不斷向後移動指標:
走一輪以後,步長h從4變為1:
程式碼實現
package advancedsort;
import java.util.Arrays;
public class AdvancedSortTest1 {
public static void main(String[] args) {
int[] arr = {99,88,77,66,55,44,33,22,11,9,8,7,6,5,4,3,2,1};
// insertSort(arr);
shellSort3(arr);
}
//實現簡單的插入排序
public static void insertSort(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
for(outer=1;outer<data.length;outer++){
inner = outer;
temp = data[outer];
while (inner>0 && temp<data[inner-1]){
//當前元素比inner指標前一個元素小,需要移動元素
data[inner] = data[inner-1];
inner--;
}
//結束while時,inner-1個元素比temp小,所以將temp放到inner位置
data[inner] = temp;
}
System.out.println("排序後:"+Arrays.toString(data));
}
//實現希爾排序,間隔是上一次間隔的一半
public static void shellSort1(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
for (int step=len/2;step>0;step=step/2){
//對每一個step進行一次迴圈
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
//當temp小於等於data[inner-step]時結束迴圈
data[inner] = temp;
}
System.out.println("間隔數是"+step+"時,陣列的排序:"+Arrays.toString(data));
}
System.out.println("排序後:"+Arrays.toString(data));
}
//實現希爾排序,間隔是上一次間隔的2.2
public static void shellSort2(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
for (int step=(int)(len/2.2);step>0;step=(int)(step/2.2)){
//對每一個step進行一次迴圈
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
data[inner] = temp;
}
System.out.println("間隔數是"+step+"時,陣列的排序:"+Arrays.toString(data));
}
System.out.println("排序後:"+Arrays.toString(data));
}
//實現希爾排序,其中step = (step-1)/3
public static void shellSort3(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
int step = 1;
//計算初始化間隔
while (step<=len/3){
step = step*3+1;
}
while (step>0){
//對每一個step進行一次迴圈
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
data[inner] = temp;
}
System.out.println("間隔數是"+step+"時,陣列的排序:"+Arrays.toString(data));
step = (step-1)/3;
}
System.out.println("排序後:"+Arrays.toString(data));
}
}