計數排序(BitMap實現)


計數排序假設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;
}



發佈了35 篇原創文章 · 獲贊 35 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章