排序演算法--插入篇(直接插入,二分插入,希爾)
一、插入類排序
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.表名排序已完成。