這篇文章主要介紹常規的排序算法,包括選擇排序、插入排序、冒泡排序、希爾排序等。
關於快速排序的詳解可參見:快速排序算法Java詳解
關於堆排序的詳解可以參見:堆排序算法Java詳解
關於歸併排序的詳解可參見:歸併排序算法Java詳解
關於基數排序的詳解可參見:基數排序算法Java詳解
0 輔助函數
在所有排序中可能都要用到的輔助函數在這裏首先列出來。
//交換
private void exchange(int[] items, int a, int b) {
int t;
t = items[a];
items[a] = items[b];
items[b] = t;
}
//比較,然後交換。如果items[b]<items[a],則交換這兩個值
private void compexch(int[] items, int a, int b) {
if(items[b] < items[a])
exchange(items, a, b);
}
1 選擇排序
選擇排序是最簡單的排序算法。首先,選出數組中最小的元素,將它與數組中第一個元素進行交換;然後找出次小的元素,並將它與數組中第二個元素進行交換。按照這種方法一直進行下去,知道整個數組完成排序。
例如如下的數組,前4個已經在正確的位置上了,後4個暫時無序:
當i=l+4時,啓動內循環,找到了最小的一個元素15。然後將15和22調換,得到如下的數組。此時前5個事有序且處於正確位置的,後三個是無序的。
重複上述步驟,直到全部循環完成,整個數組也就變成有序的了。選擇排序算法的Java實現如下:
public void selectionSort(int[] items, int l, int r) {
for(int i=l; i<r; i++) {
int min = i;
for(int j=i+1; j<=r; j++) //找到從i+1到r中最小的一個值,該值的下標爲min
if(items[j] < items[min])
min = j;
exchange(items, i, min); //交換
}
}
選擇排序有一個缺點,它運行的時間對文件中已有序的部分依賴較少,即沒有很好的利用已經有序的部分。所以其最壞和最好的時間複雜度都是O(n^2)。
2 插入排序
我們在打牌時候使用的排序方式通常就是插入排序,摸牌時每次只考慮一張牌,將牌插入已經排好了序的牌的適當位置,插入後手中的牌還是有序的。插入排序的基本操作就是將一個記錄插入已經排好序的有序表中,從而得到一個新的、記錄數增1 的有序表。
例如如下的數組,前4個已經有序,後4個暫時無序:
在對14進行插入排序後,數組的情況如下,此時前5個是有序的,後3個是無序的。
插入排序的Java代碼實現如下:
public void insertionSort(int[] items, int l, int r) {
for(int i=r; i>l; i--) compexch(items, i-1, i);
//上面是:將最小的元素挪到最前面的位置,將它作爲“觀察哨關鍵字”,這樣可以使後面的循環更快
for(int i=l+2; i<=r; i++) {
int j = i;
int v = items[i];
while(v < items[j-1]) { //元素一直後移
items[j] = items[j-1];
j--;
}
items[j] = v;
}
}
由上述代碼可以看到,在數組基本有序或完全有序時可以保證時間複雜度爲O(N)。而在最壞的情況下(數組逆序),時間複雜度爲O(N^2)。其平均時間複雜度爲O(N^2)。3 冒泡排序
冒泡排序的思路非常簡單:遍歷文件,如果近鄰的兩個元素大小順序不對,就將兩者交換,重複這樣的操作指導整個文件排好序。
對於l到r-1之間的值,在內部循環過程中,逐漸將較小的值“冒”到前面。在外循環遍歷的過程中,前i個元素已經處在了正確的位置上了。
例如如下的數組,前4個已經在正確的位置上了,後4個暫時無序:
當i=l+4時,啓動內循環,j=r。先15和16判斷,15<16,不用調換位置;然後j--,20和15判斷,20>15,調換位置:
再j--,15和22判斷;22>15,調換位置:
當再j--時,j與i重合,內部循環結束,15已經“冒”到了前端,此時前5個元素的位置是正確的,後三個無序。
重複上述循環,即可完成冒泡排序。冒泡排序的Java實現代碼如下。
public void bubbleSort(int[] items, int l, int r) {
for(int i=l; i<r; i++) {
for(int j=r; j>i; j--)
compexch(items, j-1, j);
}
}
雖然算法思路簡單,但是冒泡排序的執行速度要比插入排序或選擇排序要慢,所以在實際應用中並不是非常多。
4 希爾排序
希爾排序又稱“縮小增量排序”,它也是屬於插入排序類的方法,但是在效率上較插入排序有較大改進。對於直接插入排序,其時間複雜度爲O(n^2),但是當待排序記錄的順序正確時,其時間複雜度可提高至O(n)。希爾排序的基本思想是:先將整個待排序記錄序列分隔成若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行一次直接插入排序。
例如如下的數組,一開始時分成以5爲間隔劃分子序列{R0,R5},{R1,R6},...。然後對每個子序列進行插入排序,得到一趟排序結果。然後在對一趟排序結果以3爲間隔劃分子序列,並對每個子序列進行插入排序。最後以1爲間隔劃分子序列(以1爲間隔即不劃分)進行一次插入排序。
希爾排序的Java實現代碼如下。
public void shellSort(int[] items) { //時間複雜度:O(n^1.3)
//依次將數組分隔成5個子序列、3個子序列和1個子序列(1個子序列其實就是不劃分)
int dlka[] = {5, 3, 1};
for(int i=0; i<dlka.length; i++)
shellInsert(items, dlka[i]); //增量爲dlka[i]的希爾插入排序
}
//對數組items進行一趟希爾插入排序。該排序方式與直接插入排序相比,不同之處在於:
//前後記錄位置的增量不是1,而是dk
private void shellInsert(int[] items, int dk) {
int i, j, t;
for(i=dk; i< items.length; i++) {
if(items[i] < items[i - dk]) {
t = items[i]; //暫存
for(j=i-dk; j>=0; j-=dk) {
if(items[j] > t)
items[j + dk] = items[j]; //記錄後移
else
break;
}
items[j + dk] = t; //插入
}
}
}
希爾排序的時間複雜度爲O(n^1.3)(根據《數據結構(C語言版)》)。需要注意的是,增量dlka中最後一個值必須是1,且所有的增量必須都是質數.5 各類排序的比較
下面以表格形式從平均時間、最壞情況、輔助存儲和穩定性的角度,對各種內部排序方法進行比較。
上表中:
關於快速排序的詳解可參見:快速排序算法Java詳解
關於堆排序的詳解可以參見:堆排序算法Java詳解
關於歸併排序的詳解可參見:歸併排序算法Java詳解
關於基數排序的詳解可參見:基數排序算法Java詳解全文完。轉載請註明出處。
參考文獻
1. 嚴蔚敏,數據結構(C語言版),清華大學出版社.
2.Robert Sedgewick,算法:C語言實現,機械工業出版社.