計數排序假設n個輸入元素中的每一個都是介於0到k之間的整數,對每一個輸入元素x,確定出小於x的元素的個數,就可以把x直接放到最終輸出數組的位置上。例如有17個元素小於x,那麼x位於第18個輸出位置。而當有幾個元素相同時,方案需要修改,不能把他們放在同一個輸出位置上。也就是說,計數排序比較適合元素之間沒有重複的情況。
就時間效率來看,計數排序幾乎是只需要掃描一遍數組就可以將數放在正確的位置上,這比快速排序,堆排序等時間複雜度爲nlog(n)快得多(時間複雜度爲n),但時間上的縮短將會導致空間上的支出。對於計數排序,可以打個比方,假設輸入的數據的最大值爲1000,那麼新開闢的數組必然要有1000個空間,因爲排序的過程可以看成一種過程:假設輸入250,那麼就將這個數組的第250號位置記爲1,而其他的初始化爲0(當然,這樣的話就不能允許重複的元素出現,所以實際的代碼會略有變化,這裏可以先這麼理解)。所以,即使輸入只有3個數,而最大的數是1000的話,那麼至少也要開闢1000個數組空間,這樣就造成了極大的資源浪費。所以,計數排序的一個缺點就是不適於處理輸入值過大的情況(當然,後面使用BitMap也就是位圖的話可以緩解這種壓力,例如32位機器的話可以使開闢的數組空間減少32倍)。
這裏先給出一般情況的計數排序的代碼:
#include<stdio.h>
#include<string.h>
//數組的最多輸入是MAX個,最大不會超過NewMax
#define Max 10
#define NewMax 100
/*程序中需要兩個數組作爲參數,數組A爲待排序的數組,數組B爲排序成功後輸出的數組,當然,兩者是一樣大的
k表示新開闢的數組C的空間*/
void Counting_Sort(int* A,int* B,int k)
{
int i,j;
int C[k];
/*將k全部初始化爲0*/
for(i=0;i<k;i++)
C[i]=0;
/*對A進行掃描,在數組C中相應的地方加上1*/
for(j=0;j<Max;j++)
C[A[j]]+=1;
/*一面這一步就是所謂的計算“比他小”的元素有幾個,就是通過不斷的累加得到*/
for(i=1;i<k;i++)
C[i]=C[i]+C[i-1];
/*這一步向B數組輸入元素,需要注意輸入的方法
方向是從大到小,原因就在於輸入一個後,那麼對於”比他大“的元素來說,”比他小“的就少了一個了
從左網友的話,右邊所有的元素都要一次減1,而從右向左就只需要把自己減去1就可以了
當然,注意下B的修改方式*/
for(j=Max-1;j>=0;j--)
{
B[C[A[j]]-1]=A[j];
C[A[j]]-=1;
}
}
//主程序進行測試
int main(void)
{
int A[Max]={23,54,99,56,23,5,78,12,3,56};
int B[Max]={};
Counting_Sort(A,B,NewMax);
int i;
for(i=0;i<Max;i++)
printf("%d ",B[i]);
printf("\n");
return 0;
}
當然了,新開闢的空間的大小令人無法忍受,而使用BitMap的話,雖然不能完全不需要新開闢空間,但卻能夠大大減小新空間的開支。
所謂的BitMap,其實就是一組位的集合,例如32位的int類型可以將它拆分成一個32個0/1的集合,上面的程序中C數組的每一個元素是用int來實現的,那麼BitMap中每一個元素是用一個位來實現的,這樣可以使空間縮小到原來的32分之1,但由於一個位只能表示0和1,那麼以BitMap實現的計數排序就必然不允許有重複的元素出現了。
下面給出以BitMap實現計數排序的代碼(位操作代碼確實很蛋疼。。。好在我在幾乎每一行代碼都寫了註釋了,還看不懂拿塊豆腐撞死算了= =):
#include<stdio.h>
#include<string.h>
#define MAX 10//測試數組的最大空間
#define BITSPERWORD 32//32位的位的集合,就是說是以32個元素爲一組
#define SHIFT 5//偏移量5,什麼是偏移量?將某個數右移5爲是不是相當於除以32了呢?
#define MASK 0x1F//除去了32,那麼還需要獲取餘數,就拿這貨來做下位運算就行了
#define N 1000000//最大的數這裏設置成1000000
int a[ 1 + N/BITSPERWORD ];//需要1000000個元素集合,那麼換算成普通的int數組,需要多少個呢?,只需要除個32加1就行了
/*就是把數組裏頭清零,沒什麼特別的*/
void clear()
{
memset( a, 0, sizeof(a) );
}
/*下面這個函數是重點,傳入一個int值,在位圖中特定部分置1
所謂i>>SHIFT,就是確定是在哪一個int裏面的位要做修改
那麼,i&MASK是神馬意思呢?想一下MASK的值是多少?0x1F,化成10進制就是31,一個32位的數,和這玩意兒與一下,
相當於是得到了i除以32後的餘數,而讓1左移那麼多爲也可以理解了:不要把它想象成單獨的1,而是一個32位的1,前頭全是0,
左移那麼多位相當於就是在特定的位置置1了
在整個數組中置1的具體實現就是或操作一下就行了*/
void set( int i )
{
a[ i>>SHIFT ] |= ( 1<<( i&MASK ) );
}
/*下面這個函數是把特定的位置清零的,具體分析和上面的差不多,這裏就不贅述了*/
void clr( int i )
{
a[ i>>SHIFT ] &= ~(1<<( i&MASK ) );
}
/*測試某一個位是否爲1,1<<( i&MASK )就是這個數正常情況下該置一的地方
a[ i>>SHIFT ]是之前修改過的地方,右邊的那一位必然爲1,只有左邊的先前置1了,相與之後才能返回非0值*/
int test( int i )
{
return a[ i>>SHIFT ] & ( 1<<( i&MASK ) );
}
//主函數進行測試
int main(void)
{
int i;
clear();
int A[MAX]={123,2345,767554,23,5,5467,234564,125,54657,99};
for(i=0;i<MAX;i++)
set(A[i]);
for( i = 0; i < N; i++ )
{
if(test(i))
printf("%d ",i);
}
printf("\n");
return 0;
}