計數排序
前面已經介紹過的所有排序,包括插入排序、選擇排序、冒泡排序、歸并排序、雞尾酒排序、希爾排序、快速排序和堆排序都是比較排序,因為在他們的排序過程中,都需要經過比較,比較元素的大小然後調整順序。可以證明,在最壞情況下,任何比較排序都需要經過 Ω ( n log n ) \Omega(n \log n) Ω(nlogn) 次比較,是以比較排序的時間複雜度最好也就能到 O ( n log n ) O(n \log n) O(nlogn) 了。
那如果我們想進一步降低時間複雜度怎麼辦呢?自然我們就不能用比較排序了。那麼有不比較就能排序的方法嗎?答案是有,還不止一種。這一篇我們就先講一種:計數排序。
原理
計數排序的思想是對每一個元素,确定不大于它的元素個數,然後把它放到對應的位置就行了。如同一個班的學生從低到高排序,對一個學生來說,如果知道班上有10個人不比他高,那麼他就應該排到第11位。
如果有相同的元素,這裡還需要一點小技巧。比如有3個相同的元素,則要把這3個挨着放,最好能保持其穩定,即雖然都是一樣大小,但之前在前面的排序完還是在前面,3個元素的相對位置不變。
具體步驟如下:
- 用一個數組記錄每個元素分别有幾個。
- 計算出對每個元素分别有多少個元素不大于它。
- 把每個元素按位置放好,若有相同值的元素,注意要調整其位置以避免相同元素放到相同位置。
實作
按照以上原理我們來用代碼實作。
下面就是用C語言實作的代碼。
- 要排序的數組a有n個元素。注意a中所有元素值都在[0,k]範圍内。
- 數組c先記錄每個元素分别有幾個,後記錄對每個元素分别有多少個元素不大于它。
- 數組b用于儲存排序結果。
- 最終将排序結果複制到a中。
/* a中所有元素值都在[0,k]範圍内 */
void count_sort(int a[], int n, int k)
{
int *b, *c;
int i;
b = (int*)malloc(sizeof(int)*n);
if (b==NULL) return;
c = (int*)malloc(sizeof(int)*(k+1));
if (c==NULL) {
free(b);
return;
}
memset(c, 0, sizeof(int)*(k+1));
for (i=0; i<n; i++) {
c[a[i]]++;
} //此時c[i]中存的是a中有幾個i
for (i=1; i<=k; i++) {
c[i] += c[i-1];
} //此時c[i]中存的是有幾個元素<=i
for (i=n-1; i>=0; i--) {
b[c[a[i]]-1] = a[i]; //将a[i]放入b中正确的位置
c[a[i]]--; //調整b的位置指針,避免相同元素放到相同位置
}
memcpy(a, b, sizeof(int)*n); //将排序結果複制到a中
free(c);
free(b);
}
為了驗證此函數的效果,加上了如下輔助代碼,對3個數組進行排序,運作結果在最後,可見排序成功。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20
void show_array(int a[], int n);
void count_sort(int a[], int n, int k);
void main()
{
int array1[SIZE_ARRAY_1]={1,4,2,9,0};
int array2[SIZE_ARRAY_2]={10,5,2,1,9,2};
int array3[SIZE_ARRAY_3];
for(int i=0; i<SIZE_ARRAY_3; i++) {
array3[i] = (int)((10.0*rand())/(RAND_MAX+1.0));
}
printf("Before sort, ");
show_array(array1, SIZE_ARRAY_1);
count_sort(array1, SIZE_ARRAY_1, 9);
printf("After sort, ");
show_array(array1, SIZE_ARRAY_1);
printf("Before sort, ");
show_array(array2, SIZE_ARRAY_2);
count_sort(array2, SIZE_ARRAY_2, 10);
printf("After sort, ");
show_array(array2, SIZE_ARRAY_2);
printf("Before sort, ");
show_array(array3, SIZE_ARRAY_3);
count_sort(array3, SIZE_ARRAY_3, 10);
printf("After sort, ");
show_array(array3, SIZE_ARRAY_3);
}
void show_array(int a[], int n)
{
if(n>0)
printf("This array has %d items: ", n);
else
printf("Error: array size should bigger than zero.\n");
for(int i=0; i<n; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
運作結果:
Before sort, This array has 5 items: 1 4 2 9 0
After sort, This array has 5 items: 0 1 2 4 9
Before sort, This array has 6 items: 10 5 2 1 9 2
After sort, This array has 6 items: 1 2 2 5 9 10
Before sort, This array has 20 items: 8 3 7 7 9 1 3 7 2 5 4 6 3 5 9 9 6 7 1 6
After sort, This array has 20 items: 1 1 2 3 3 3 4 5 5 6 6 6 7 7 7 7 8 9 9 9
分析
時間複雜度
從代碼可見,count_sort中隻有一層循環,有三次,量級分别是 n n n、 k k k、 n n n,是以其時間複雜度為 O ( n + k ) O(n+k) O(n+k),若 k = O ( n ) k = O(n) k=O(n),則計數排序的時間複雜度為 O ( n ) O(n) O(n)。
空間複雜度
因為計數排序需要兩個輔助數組b和c來存儲中間結果,是以其空間複雜度為 O ( n + k ) O(n+k) O(n+k),若 k = O ( n ) k = O(n) k=O(n),則計數排序的空間複雜度為 O ( n ) O(n) O(n)。
要注意的是計數排序要求所有資料都是屬于某個小區間的整數。如果範圍太大,即 k k k 太大,則時間複雜度、空間複雜度都會變成增加,會影響排序效率。
穩定性
穩定。