參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure
1.直接插入排序
1.1基本思想
一趟直接插入排序的基本思想: 將記錄L.r[i]插入到有序子序列L.r[1..i-1]中,使記錄的有序序列從L.r[1..i-1]變爲L.r[1..i]。 完成這個“插入”分三步進行:
1.查找L.r[i]在有序子序列L.r [1..i-1]中的插入位置j;
2.將L.r [j..i-1]中的記錄後移一個位置;
3.將L.r [i]複製到L.r [j]的位置上。
整個排序過程進行n–1趟插入,即:先將序列中的第1個記錄着成一個有序的子序列,然後從第2個記錄起逐個插入,直至整個序列變成接關鍵字非遞減有序序列爲止。
1.2代碼實現
package sort.insertionSort;
public class StraightSort {
/**
* @param args
*/
//對順序表L做直接插入排序
public static void InsertSort(int[] L){
//先將第一個元素看成是一個有序子序列
for(int i = 2; i <= L.length-1; i++){
//在已經有序的1->i-1的子序列中插入第i個元素,以保證仍然有序,成爲一個1->i的有序子序列
L[0] = L[i];//監視哨
int j = i-1;
/*
for(; j > 0; j--){//沒有利用監視哨,仍然用j>0作爲條件以避免數組下標越界
if(L[0] < L[j])
L[j+1] = L[j];
else
break;
}
*/
for(; L[0] < L[j]; j--)
L[j+1] = L[j];//利用監視哨
//當L[0] >= <[j]時跳出循環,由於j做了一次自減,所以是L[j+1] = L[0],
//當是因爲=而跳出循環時(j後來沒有自減),L[0]插在L[j]的後面以保證了“穩定”
L[j+1] = L[0];
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 53, 27, 36, 15, 69, 42}; //0號單元未使用
InsertSort(test);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
運行結果:
1.3性能分析
直接插入排序性能分析 ,實現排序的基本操作有: (1)“比較” 關鍵字的大小 (2)“移動”記錄
對於直接插入排序:
最好情況“比較”次數:n-1;“移動”次數:2(n-1)
最壞的情況“比較”和“移動”的次數均達到最大值,分別爲:(n+2)(n-1)/2;(n+4)(n-1)/2
由於待排記錄序列是隨機的,取上述二值的平均值。所以直接插入排序的時間複雜度爲 O(n^2)。
直接插入排序是“穩定的”:關鍵碼相同的兩個記錄,在整個排序過程中,不會通過比較而相互交換。
2.折半插入排序
2.1基本思想
考慮到 L.r[1..i-1] 是按關鍵字有序的有序序列,則可以利用折半查找實現“ L.r[1…i-1]中查找 L.r[i] 的插入位置”如此實現的插入排序爲折半插入排序。折半插入排序在尋找插入位置時,不是逐個比較而是利用折半查找的原理尋找插入位置。待排序元素越多,改進效果越明顯。
2.2代碼實現
package sort.insertionSort;
public class BinaryInsertionSort {
/**
* @param args
*/
//對順序表L做折半插入排序,利用折半查找快速找到要插入的位置
public static void binaryInsertSort(int[] L){
for(int i = 2; i <= L.length-1; i++){
//利用折半查找找到要插入的位置
int low = 1, high = i-1;//在1->i-1的有序子序列中插入第i個元素,使之成爲1->i的有序子序列
L[0] = L[i];//暫存要插入的元素
while(low <= high){
int mid = (low+high)/2;
if(L[0] < L[mid])
high = mid -1;
else
//L[0] >= L[mid]
low = mid+1;//等於當成大於處理,這樣後出現的相等值就會排在後面,從而到達“穩定”
}
//此時high = low-1,且high+1即low的位置即爲要插入的位置
for(int j = i-1; j >= low; j--)
L[j+1] = L[j];
L[low] = L[0];
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 53, 27, 36, 15, 69, 42}; //0號單元未使用
binaryInsertSort(test);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
運行結果:
2.3性能分析
折半插入排序減少了關鍵字的比較次數,但記錄的移動次數不變,其時間複雜度與直接插入排序相同,時間複雜度爲O(n^2) 。折半插入排序是“穩定的”。
3.希爾排序(縮小增量排序)
3.1基本思想
希爾排序(Shell Sort)又稱爲“縮小增量排序”。其基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因爲直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。
3.2代碼實現
package sort.insertionSort;
public class ShellSort {
/**
* @param args
*/
//對順序表L做一趟希爾排序,本算法和一趟直接插入排序相比,做了如下修改:
//1.前後記錄位置的增量式dk,而不是1
//2.L[0]只是暫存單元,而不再是哨兵
public static void shellInsert(int[] L, int dk){
/*對於下面for循環的i = i+dk和i++的分析:
一趟希爾排序裏有L.length/dk個子序列,每個子序列要進行直接插入排序,即要進行L.length/dk個直接插入排序
子序列輪着來(有點併發的感覺),即第一個子序列的第2個數排完序,然後是第2個子序列的第2個數排序,然後是第3個子序列。。。
i繼續自增,然後是第1個子序列的第3個數往第一個子序列的1->2的有序子序列裏插入並排序,然後是第2個子序列的第3個數
往第2個子序列的1->2的有序子序列裏插入並排序,然後是第3個子序列的第3個數往第3個子序列的1->2的有序子序列裏插入並排序。。。
i繼續自增,然後是第1個子序列的第4個數往第一個子序列的1->3的有序子序列裏插入並排序,接着
第2個子序列的第4個數往第2個子序列的1->3的有序子序列裏插入並排序.。。。。。以此類推,直到所有的完成
*/
//相當於每個子序列的第一個數都被看成是每個子序列的有序子序列
for(int i = 1+dk; i <= L.length-1; i++){
L[0] = L[i];
//找L[i]應該插入的位置
int j = i-dk;
for(; j>0&&L[0]<L[j]; j-=dk)
L[j+dk] = L[j];
L[j+dk] = L[0];
}
}
//按增量dlta[0......len(dlta)-1]對順序表L做希爾排序
public static void shellSort(int[] L, int[] dlta){
for(int i = 0; i < dlta.length; i++)
shellInsert(L, dlta[i]);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 65, 49, 97, 25, 25, 13}; //0號單元未使用
int[] dlta = {3, 2, 1};//應使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1。
shellSort(test, dlta);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
運行結果:
3.3性能分析
雖然我們給出的算法是三層循環,最外層循環爲log2n數量級,中間的for循環是n數量級的,內循環遠遠低於n數量級,因爲當分組較多時,組內元素較少;此循環次數少;當分組較少時,組內元素增多,但已接近有序,循環次數並不增加。因此,希爾排序的時間複雜性在O(nlog2n)和O(n^2 )之間,大致爲O(n^1. 3)。
希爾排序的時間複雜度較直接插入排序低。希爾排序的分析是一個複雜的問題,因爲它的時間是和所取“增量”序列的函數密切相關。到目前爲止,還沒有求得一種最好的增量序列,但有大量的局部結論。
注意:應使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1。
由於希爾排序對每個子序列單獨比較,在比較時進行元素移動,有可能改變相同排序碼元素的原始順序,因此希爾排序是不穩定的。