1. 程式人生 > >排序演算法之希爾排序

排序演算法之希爾排序

演算法分析:

希爾排序(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在排序前後的相對次序發生了變化。  

參考文章: