計數排序的時間複雜度比快速排序,合併排序(O(nlongn))都要好O(n),但是是以空間代價換取的,並且在範圍較小的整數數中使用。
在介紹技術排序之前先補充時空權衡的思想。
時空權衡
時空權衡的思想就是以空間資源換取時間效率,在算法設計中經常遇到。
需要注意的是:並不是所有的情況下,時間和空間這兩種資源都是相互競爭的。實際上,他們可以聯合起來達到最小化。這種情況需要一個高效的數據結構,如:圖的深度或廣度遍歷算法,在鄰接矩陣中,時間效率是O(n 2 2 );在鄰接鏈表中,時間效率是O(n+e)。無論是時間角度還是空間角度來看,鄰接鏈表的效率更高,在稀疏圖中尤爲明顯。
時空權衡的思想可以分爲以下三點(以及應用算法):
- 輸入增強:計數法排序,字符串匹配的Horspool算法(最壞時間O(nm),平均O(n))、Boyer-Moore算法(最壞O(n+m)))http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
- 預構造:散列表,B樹索引
- 動態規劃
具體思想——>
輸入增強:對問題的部分或全部輸入做預處理,然後將獲得的額外信息進行存儲,以加速後面問題的求解。
預構造:使用額外空間來實現更快和更方便的數據存取。與輸入增強不同,這種技術只涉及到存取結構。
動態規劃:這個策略的基礎是把給定問題中重複子問題的解記錄在表中。
計數排序
計數排序作爲輸入增強的例子,其思路是非常簡單的:針對待排序列表中的每個元素,算出列表中小於該元素的元素個數,並把結果記錄在一張表中。這個“個數”就是該元素在有序列表中的位置。(下面幾種方法的不同就在於這個“個數”是如何得到的)如對某個元素來說,這個個數是10,它應該排在有序數組第11個位置(如果從0開始計數,則下標是10)。
幾個計數排序的比較——>
- 通用的計數排序:時間O(n);空間O(n+k),k是最大元素值
- 比較計數排序:時間O(n 2 2 );空間O(n)
- 分佈計數法排序:時間O(n)
通用的計數排序
- 列表元素個數爲n,最大元素值爲k
- 開闢數組count[k]用於記錄元素的個數
- 開闢數組temp[n]用於存儲有限列表
- 場合:適用於個數較多,但元素大小範圍較小的整數列表
代碼——>
public class GeneralCountingSort {
public static void main(String[] args) {
test();
}
/** * * @param array正整型待排序數組 * @return */
public static int[] conutsorting(int[] array) {
//數組長度
int n = array.length;
//元素最大值,數組範圍
int k = 0;
for (int i = 0; i < n; i++) {
k = array[i] > k ? array[i] : k;
}
//記錄元素個數
int[] count = new int[k+1];
for (int i = 0; i < n; i++) {
count[array[i]]++;
}
//元素按個數累加得到序號,即小於該元素的“個數”
for (int i = 1; i <= k; i++) {
count[i] += count[i-1];
}
//排序
int[] temp = new int[n];
for (int i = 0; i < n; i++) {
count[array[i]]--;//①相同元素放置②下標從0開始
temp[count[array[i]]] = array[i];
}
return temp;
}
/** * 測試 */
public static void test() {
int[] array = {7, 4, 2, 1, 5, 3, 1, 5};
int[] result = conutsorting(array);
for (int i = 0; i < result.length; i++) {
System.out.print(result[i] + " ");
}
}
}
比較計數排序
比較計數排序開闢空間爲O(n),所以這裏的計數方式與通用計數排序不同,仍要依次比較後才記錄,時間複雜度爲O(n 2 2 )。
例子——>
數組A[0..5]: | 62 | 31 | 84 | 96 | 19 | 47 |
---|---|---|---|---|---|---|
初始記錄數組count[]: | 0 | 0 | 0 | 0 | 0 | 0 |
i=0時 | 3 | 0 | 1 | 1 | 0 | 0 |
i=1時 | 1 | 2 | 2 | 0 | 1 | |
i=2時 | 4 | 3 | 0 | 1 | ||
i=3時 | 5 | 0 | 1 | |||
i=4時 | 0 | 2 | ||||
最終狀態 | 3 | 1 | 4 | 5 | 0 | 2 |
數組S[0..5]: | 19 | 31 | 47 | 62 | 84 | 96 |
僞代碼——>
ComparisonCountingSort(A[0..n-1])
//用比較計數法對數組排序
//輸入:待排序數組A[0..n-1]
//輸出:將A中的元素按升序排列的數組S0..n-1]
for(i = 0; i <= n-1; i++) do
Count[i] ← 0;
for(i = 0; i <= n-2; i++) do
for(j = i+1; j <= n-1; i++) do
if(A[i] < A[j])
Count[j] ← Count[i] + 1;
else
Count[i] ← Count[i] + 1;
for(i = 0; i <= n-1; i++) do
S[Count[i]←A[i]];
return S;
分佈計數法排序
思想:如果帶排序的元素的值都來自於一個已知的小範圍,即元素的值位於下界l和上界u之間的整數,可以計算元素出現的頻率,記錄在F[0..u-l]中,這樣前F[0]個位置填入l,接着F[1]個位置填入l+1,以此類推。
例子——>
待排序數組:13 11 12 13 12 12
數組值 | 11 | 12 | 13 |
---|---|---|---|
頻率 | 1 | 3 | 2 |
分佈值 | 1 | 4 | 6 |
處理過程:D[0..2]表示分佈值,從右往左更容易(即從12開始)
________ ___D[0..2]___ ___S[0..5]___
A[5] = 12 1 4* 6 - - - 12 - -
A[4] = 12 1 3* 6 - - 12 - - -
A[3] = 13 1 2 6* - - - - - 13
A[2] = 12 1 2* 5 - 12 - - - -
A[1] = 11 1* 1 5 11 - - - - -
A[0] = 13 0 1 5* - - - - 13 -
僞代碼——>
DistributionCountingSort(A[0..n-1],l,u)
//分佈計數法,對來自於有限範圍整數的一個數組進行排序
//輸入:數組A[0..n-1],數組中的整數位於l和u之間(l<=u)
//輸出:A中元素構成的非降序數組S[0..n-1]
for(j = 0; j <= u-l; j++) do D[j] ← 0;//初始化頻率數組
for(i = 0; i <= n-1; i++) do D[A[i]-l] ← D[A[i]-l] + 1;//計算頻率值
for(j = 0; j <= u-l; j++) do D[j] ← D[j-1] + D[j];//重用於分佈
for(i = n-1; i >= 0; i--) do
j ←A[i]-l;
S[D[j]-1] ← A[i];
D[j] ← D[j] - 1;//分佈值減1,得到下個相同元素
return S;
參考:
- 《算法設計與分析基礎》
- 計數排序詳解