線性排序算法(計數排序,基數排序,桶排序)分析及實現

寫在前面

大家都知道的是,基於比較的排序算法的時間複雜度的下界是 O(n log(n))。這一結論是可以證明的,所以在基於比較的算法中是找不到時間複雜度爲 O(n)的算法的。這時候,非基於比較的算法,如計數排序、基數排序和桶排序,是可以突破這個下界的。但是,非基於比較的排序的使用限制卻是較多的,如計數排序僅能對較小整數進行排序,且要求排序的數據的規模不能過大;基數排序可以對長整數進行排序,但是不適用於浮點數;桶排序可以對浮點數進行排序。

關於時間複雜度的問題在後續將陸續闡明。

基於比較的排序算法可以參考幾種常用的排序算法分析及實現


計數排序


基本思想:計數排序要求數據的範圍在0到k之間的整數,引入了一個輔助數組C,數組C的大小爲k,存儲了待排序數組中值小於等於C的索引值的個數。

1. 統計出待排序數組值爲i的元素個數,存入輔助數組C的第i項中

2. 對C中的數據進行累加,每一項的值等於本項值加上前一項的值

3. 反向掃描待排序數組,每掃描一項m,將其存入新的數組的第C(m)項,對C(m)的值減一


具體過程




具體代碼

	/**
	 * 計數排序
	 * 
	 * @param A
	 *            要排序的數組
	 * @param k
	 *            數組中最大的元素值
	 * @return
	 */
	public static int[] countingSort(int[] A, int k) {
		int n = A.length;
		int[] C = new int[k];
		int[] B = new int[n];
		int i;

		for (i = 0; i < k; i++) { // 初始化輔助數組
			C[i] = 0;
		}

		for (i = 0; i < n; i++) { // 計數數組A中值等於C數組下標的個數
			C[A[i]]++;
		}

		for (i = 1; i < k; i++) { // 計數數組A中值小於等於C數組下標的個數
			C[i] += C[i - 1];
		}

		for (i = n - 1; i >= 0; i--) {
			B[C[A[i]] - 1] = A[i];
			C[A[i]]--;
		}
		return B;
	}


簡要分析

  • 計數排序僅適合於小範圍的數據進行排序
  • 不能對浮點數進行排序
  • 時間複雜度爲 O(n)
  • 計數排序是穩定的(排序後值相同的元素相對於原先的位置是不會發生變化的)


基數排序


基本思想:基數排序是將整數按位進行排序,從低位開始,對每一位使用穩定的排序算法如計數排序進行排序,直到最高位排序完成,所有元素排序完成。

在這裏需要注意的是,我實現的基數排序是需要傳入整數數組中位數最長的元素的位數。否則在對每一位進行排序時,僅將每個整數的那一位均爲零作爲程序結束的標誌,將會出現如果測試數據的第某一位全爲零,則算法執行停止,出現錯誤。


具體過程




具體代碼

	/**
	 * 對A中的數據(即整數的某一位組成的數組)進行計數排序,然後將結果保存爲已按照某一位排序的原始數據
	 * 
	 * @param A
	 *            原始數據的某一位的數組
	 * @param k
	 *            每一位的範圍,一般傳入9(整數某位的大小至多爲9)
	 * @param D
	 *            原始數據數組
	 * @return
	 */
	public static int[] countingSort_r(int[] A, int k, int[] D) {
		int n = A.length;
		int[] C = new int[k];
		int[] B = new int[n];
		int i;

		for (i = 0; i < k; i++) { // 初始化輔助數組
			C[i] = 0;
		}

		for (i = 0; i < n; i++) { // 計數數組A中值等於C數組下標的個數
			C[A[i]]++;
		}

		for (i = 1; i < k; i++) { // 計數數組A中值小於等於C數組下標的個數
			C[i] += C[i - 1];
		}

		for (i = n - 1; i >= 0; i--) {
			B[C[A[i]] - 1] = D[i]; // 注意這裏
			C[A[i]]--;
		}
		return B;
	}

	/**
	 * 計數排序
	 * 
	 * @param d
	 *            待排序的數組
	 */
	public static void radixSort(int[] d, int wordLength) {
		int n = d.length;
		int[] tmp = new int[n];
		int base = 1;

		while (wordLength != 0) {
			base *= 10;

			for (int i = 0; i < n; i++) { // 分離整數的數位
				tmp[i] = d[i] % base;
				tmp[i] /= base / 10;
			}
			int[] sorted = countingSort_r(tmp, 10, d); // 對每一位進行計數排序
			for (int i = 0; i < n; i++) {
				d[i] = sorted[i];
			}
			wordLength--;
		}
	}


簡要分析

  • 基數排序僅可排序整數
  • 與計數排序不同的是,基數排序可以排序大整數
  • 對每一位進行排序時,需要使用穩定的排序算法,保證在排序高位時低位的順序不會變
  • 時間複雜度爲 O(n)


桶排序


基本思想:對於一組程度爲N的待排序數據,將這些數據劃分爲M個區間(即放入M個桶中)。根據某種映射函數,將這N個數據放入M個桶中。然後對每個桶中的數據進行排序,最後依次輸出,得到已排序數據。桶排序要求待排序的元素都屬於一個固定的且有限的區間範圍內。

對於映射函數的選取,在這裏,假設數據都在[0,1)區間中,所以映射函數可以爲 f(x) = x * 10。


具體過程



具體代碼

	/**
	 * 桶排序(要求待排數組元素的大小範圍在[0,1)之間)
	 * 
	 * @param d
	 *            待排序的數組
	 */
	public static void bucketSort(double[] d) {
		int n = d.length;
		double[][] bucket = new double[10][10]; // 用一個二維數組來表示桶

		for (int i = 0; i < 10; i++) {
			bucket[i] = new double[10];
		}

		int[] count = new int[10];
		for (int i = 0; i < 10; i++) { // 將每個桶的元素個數初始化爲零
			count[i] = 0;
		}

		for (int i = 0; i < n; i++) {
			double tmp = d[i];
			int index = (int) (tmp * 10); // 將數據元素的小數點後第一位作爲桶的索引號
			bucket[index][count[index]] = tmp;
			int j = count[index]++;

			while (j > 0 && tmp < bucket[index][j - 1]) // 對同一個桶內的元素進行插入排序
			{
				bucket[index][j] = bucket[index][j - 1];
				j--;
			}
			bucket[index][j] = tmp;
		}

		int m = 0;
		for (int i = 0; i < 10; i++) // 按序將桶內元素全部讀出來
		{
			for (int j = 0; j < count[i]; j++) {
				d[m] = bucket[i][j];
				m++;
			}
		}
	}


簡要分析

  • 桶排序對待排序數據的要求必須在某個指定的範圍內
  • 可以用來排序浮點數,與計數排序和基數排序不同
  • 桶排序是穩定的
  • 速度快,但是耗空間

小結

從整體上來看,這三種排序算法都對數據的要求較爲嚴格,不如其他常用的基於比較的排序算法那樣普遍通用。這也說明了如果可以充分的利用起數據的特性,可以使計數排序的時間複雜度依賴於數據的範圍,桶排序還依賴於空間的大小和數據的分佈情況,基數排序一般情況下是需要用到計數排序。


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