1. 程式人生 > >排序演算法--插入篇(直接插入,二分插入,希爾)

排序演算法--插入篇(直接插入,二分插入,希爾)

一、插入類排序

1.直接插入排序

演算法思想:基本操作是將第i個記錄插入到前面第i-1個已排序好的記錄中。具體過程:把第i個記錄的關鍵字Ki,依次與前面Ki-1,Ki-1,...,K1比較,將所有關鍵字大於Ki的記錄依次先後移動一個位置,直到遇到一個關鍵字Kj小於或等於關鍵字Ki,此時Kj後面必為空把第i個元素插入進去即可。

private static void InsSort(int[] a) {
	int key=0,j=0;
	for (int i = 1; i < a.length; i++) {
		key=a[i];//將待插入記錄存放到監視哨key中
		j=i-1;
		while(j>=0&&key<a[j]){//尋找插入位置
			a[j+1]=a[j];
			j=j-1;
		}
		a[j+1]=key;
	}
}

演算法分析:從空間角度來看,只需要一個輔助空間key,空間複雜度S(n)=O(1);

從時間角度來看,最好情況while迴圈只進行一次,總比較次數為n-1次,最壞是比較(n+2)(n-1)/2,時間複雜度為T(n)=O(n^2)

穩定性:穩定

改進:待排序記錄數目較大時,可以在此基礎上,從“減少關鍵字”和“移動記錄”兩種操作的次數進一步改進

2.折半插入排序

演算法思想:在有序記錄a[1,..i-1]中,採用折半查詢確定插入的位置

	private static void BinSort(int[] a) {
		int k,low,high,mid;
		for (int i = 1; i < a.length; i++) {
			k= a[i];
			low=0;
			high=i-1;
			while(low<=high){//確定插入位置
				mid=(low+high)/2;
				if(k<a[mid]) high=mid-1;
				else low=mid+1;
			}
			for(int j=i-1;j>=low;--j) a[j+1]=a[j];//記錄依次向後移動
			a[low]=k;//插入記錄
		}
		
	}

演算法分析:採用折半插入排序法,可減少關鍵字的比較次數。每插入一個元素,需要比較的次數最大為折半判定樹的深度,如插入第i個元素時,設i=2^j,則需進行log2i次比較,因此插入n-1個元素的平均關鍵字的比較次數為nlog2n。

空間複雜度:O(1)        時間複雜度O(n^2)    穩定

3.希爾排序

演算法思想:對直接插入排序的改進,又稱縮小增量排序法。先將待排序記錄序列分割成若干個“較稀疏的”子序列,分別進行直接插入排序。最後再對全部記錄進行一次直接插入排序。

具體流程:

1.將包含n個元素的陣列,分成n/2個數組序列,第一個資料和第n/2+1個數據為一對

2.對每對資料進行比較和交換,排好順序

3.然後分成n/4個數組序列,再次排序

4.不斷重複以上過程,隨著序列減少並直至為1,排序完成。

假設初始資料為:【25,11,45,13,66,9】,

1.第一輪排序,將該陣列分為6/2=3個數組序列,第1個數據和第4個數據為一對,第2個數據和第5個數據為一對,第3個和第6為一對,排序後[13,11,9,25,66,45]

2.第二輪排序,將上輪排序分為6/4=1個數組序列,[9,11,13,25,45,66]

    private static void ShellSort(int[] a) {
        if(a == null || a.length <= 1){
             return;
        }
        //增量
        int increNum = a.length/2;
        while(increNum >=1){
            for(int i=0;i<a.length;i++){
                //進行插入排序
                for(int j=i;j<a.length-increNum;j=j+increNum){
                    if(a[j]>a[j+increNum]){
                        int temple = a[j];
                        a[j] = a[j+increNum];
                        a[j+increNum] = temple;
                    }
                }
            }
            //設定新的增量
            increNum = increNum/2;
        }
    }

演算法分析:當增量為1時,與直接插入排序過程相同。在希爾排序中,各子序列的排序過程相對獨立,但具體實現時,並不是先對一個子序列進行完全排序,再對另一個子序列進行排序。在順序掃描整個待排序記錄序列時,各子序列的元素將會反覆輪流出現。根據這個特點,希爾排序從第一個子序列的第二個元素開始,順序掃描待排序記錄序列,對首先出現的各子序列的第二個元素,分別在各子序列中進行插入處理;然後對隨後出現的各子序列的第3個元素,分別在各子序列中進行插入處理,知道處理完各子序列的最後一個元素。

時間複雜度O(n^1.25)  空間複雜度O(1)    不穩定

擴:

增量的取法:關於增量d的取法,最初希爾(Shell)提出取d=[n/2],再取d=[d/2],知道d=1為止。該思路的缺點是,奇數位置的元素在最後一步才會與偶數位置的元素進行比較,使得希爾排序效率降低。因此後來Knuth提出d=[d/3]+1.此外還有其他多種取法,但沒有最優性證明。

逆轉數:為了分析希爾排序的優越性,這裡引出逆轉數的概念。對於待排序序列中的某個記錄的關鍵字,它的逆轉數是指在它之前比此關鍵字大的關鍵書的個數。

關鍵字 46 55 13 42 94 17 05 70
逆轉數(Bi) 0 0 2 2 0 4 6 1

在未經過一次希爾排序之前,它的逆轉數之和為0+0+2+2+0+4+6+1=15,之後經過一次希爾排序後得到[46,17,05,42,94,55,13,70],其中17和55位置發生了變化,對17之前和55之後的關鍵字的逆轉數無影響,而兩個關鍵字本身以及介於這兩個關鍵字之間的逆轉數都會減少。對於17本身而言,其逆轉數必減少l,而關鍵字17和55之間的關鍵字,若其值大小界於17和55之間,則這些關鍵字的逆轉數一定會減少。即假設被調換位置的兩個關鍵字之間有l個界於兩個關鍵字之間的數,則逆轉數之和一定會減小2l+1.

以上面序列為例,初始逆轉和為15,經d1=4後,逆轉數1+2+1+0+1+5+1=11;經d2=2後,逆轉數0+1+0+0+0+1=2;經d3=1後,逆轉數為0.表名排序已完成。