(戀上資料結構筆記):計數排序、基數排序 、桶排序

Asinmy發表於2020-12-05

目錄

計數排序(Counting Sort)

計數排序 - 最簡單的實現

計數排序 - 改進思路

改進版程式碼實現

計數排序 - 對自定義物件排序

基數排序 (Radix Sort)

基數排序程式碼實現

基數排序-另一種思路

桶排序(Bucket Sort)

計數排序(Counting Sort)

  • 之前學習的冒泡、選擇、插入、歸併、快速、希爾、堆排序,都是基於比較的排序
    • 平均時間複雜度目前最低是O(nlogn)
  • 計數排序、桶排序、基數排序,都不是基於比較的排序它們是典型的用空間換時間,在某些時候,平均時間複雜度可以比O(nlogn)更低
  • 計數排序於1954年由Harold H.Seward提出,適合對一定範圍內的整數進行排序
  • 計數排序的核心思想
    • 統計每個整數在序列中出現的次數,進而推導出每個整數在有序序列中的索引

計數排序 - 最簡單的實現

// 找出最大值
int max = array[0];
for (int i = 1; i < array.length; i++) {
	if (array[i] > max) {
		max = array[i];
	}
} // O(n)

// 開闢記憶體空間,儲存每個整數出現的次數
int[] counts = new int[1 + max];
// 統計每個整數出現的次數
for (int i = 0; i < array.length; i++) {
	counts[array[i]]++;
} // O(n)

// 根據整數的出現次數,對整數進行排序
int index = 0;
for (int i = 0; i < counts.length; i++) {
	while (counts[i]-- > 0) {
		array[index++] = i;
	}
} // O(n)

 

  • 這個版本的實現存在以下問題
  • 無法對負整數進行排序
  • 極其浪費記憶體空間
  • 是個不穩定的排序

計數排序 - 改進思路

  • 逆序遍歷可以保持穩定性

 

  • 依次類推

改進版程式碼實現

// 找出最值
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
	if (array[i] > max) {
		max = array[i];
	}
	if (array[i] < min) {
		min = array[i];
	}
}

// 開闢記憶體空間,儲存次數
int[] counts = new int[max - min + 1];
// 統計每個整數出現的次數
for (int i = 0; i < array.length; i++) {
	counts[array[i] - min]++;
}
// 累加次數
for (int i = 1; i < counts.length; i++) {
	counts[i] += counts[i - 1];
}

// 從後往前遍歷元素,將它放到有序陣列中的合適位置
int[] newArray = new int[array.length];
for (int i = array.length - 1; i >= 0; i--) {
	newArray[--counts[array[i] - min]] = array[i];
}

// 將有序陣列賦值到array
for (int i = 0; i < newArray.length; i++) {
	array[i] = newArray[i];
}

 

  • 最好、最壞、平均時間複雜度:O(n+k)
  • 空間複雜度:O(n+k)
  • k是整數的取值範圍
  • 屬於穩定排序

計數排序 - 對自定義物件排序

Person[] persons = new Person[] {
		new Person(20, "A"),
		new Person(-13, "B"),
		new Person(17, "C"),
		new Person(12, "D"),
		new Person(-13, "E"),
		new Person(20, "F")
};

// 找出最值
int max = persons[0].age;
int min = persons[0].age;
for (int i = 1; i < persons.length; i++) {
	if (persons[i].age > max) {
		max = persons[i].age;
	}
	if (persons[i].age < min) {
		min = persons[i].age;
	}
}

// 開闢記憶體空間,儲存次數
int[] counts = new int[max - min + 1];
// 統計每個整數出現的次數
for (int i = 0; i < persons.length; i++) {
	counts[persons[i].age - min]++;
}
// 累加次數
for (int i = 1; i < counts.length; i++) {
	counts[i] += counts[i - 1];
}

// 從後往前遍歷元素,將它放到有序陣列中的合適位置
Person[] newArray = new Person[persons.length];
for (int i = persons.length - 1; i >= 0; i--) {
	newArray[--counts[persons[i].age - min]] = persons[i];
}

// 將有序陣列賦值到array
for (int i = 0; i < newArray.length; i++) {
	persons[i] = newArray[i];
}

for (int i = 0; i < persons.length; i++) {
	System.out.println(persons[i]);
}

基數排序 (Radix Sort)

 

  • 基數排序非常適合用於整數排序(尤其是非負整數),這裡只介紹對非負整數進行基數排序。
  • 執行流程:依次對個位數、十位數、百位數、千位數、萬位數…進行排序(從低位到高位

  • 個位數、十位數、百位數的取值範圍都是固定的0~9,可以使用計數排序對它們進行排序。

基數排序程式碼實現

public class RadixSort extends Sort<Integer> {

	@Override
	protected void sort() {
		// 找出最大值
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (array[i] > max) {
				max = array[i];
			}
		}
		
		// 個位數: array[i] / 1 % 10 = 3
		// 十位數:array[i] / 10 % 10 = 9
		// 百位數:array[i] / 100 % 10 = 5
		// 千位數:array[i] / 1000 % 10 = ...

		for (int divider = 1; divider <= max; divider *= 10) {
			countingSort(divider);
		}
	}
	
	protected void countingSort(int divider) {
		// 開闢記憶體空間,儲存次數
		int[] counts = new int[10];
		// 統計每個整數出現的次數
		for (int i = 0; i < array.length; i++) {
			counts[array[i] / divider % 10]++;
		}
		// 累加次數
		for (int i = 1; i < counts.length; i++) {
			counts[i] += counts[i - 1];
		}
		
		// 從後往前遍歷元素,將它放到有序陣列中的合適位置
		int[] newArray = new int[array.length];
		for (int i = array.length - 1; i >= 0; i--) {
			newArray[--counts[array[i] / divider % 10]] = array[i];
		}
		
		// 將有序陣列賦值到array
		for (int i = 0; i < newArray.length; i++) {
			array[i] = newArray[i];
		}
	}
}
  • 最好、最壞、平均時間複雜度:O(d ∗ (n + k)) ,d 是最大值的位數,k 是進位制
  • 空間複雜度:O(n + k),k 是進位制
  • 基數排序屬於穩定排序

基數排序-另一種思路

  • 複雜度與穩定性
    • 空間複雜度是 O(kn + k) ,時間複雜度是 O(dn)
    • d 是最大值的位數,k 是進位制

桶排序(Bucket Sort)

  • 執行流程:
    • ① 建立一定數量的桶(比如用陣列、連結串列作為桶)
    • ② 按照一定的規則(不同型別的資料,規則不同),將序列中的元素均勻分配到對應的桶
    • ③ 分別對每個桶進行單獨排序
    • ④ 將所有非空桶的元素合併成有序序列
  • 元素在桶中的索引:元素值 * 元素數量

  • 實現

  • 複雜度與穩定性
    • 空間複雜度:O(n + m),m 是桶的數量
    • 時間複雜度:

  • 桶排序屬於穩定排序

相關文章