常規排序算法Java詳解

這篇文章主要介紹常規的排序算法,包括選擇排序、插入排序、冒泡排序、希爾排序等。


關於快速排序的詳解可參見:快速排序算法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語言實現,機械工業出版社.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章