計數排序(C語言版)
先說說計數排序的思想:
計數排序假定待排序的所有元素都是介於0到K之間的整數;計數排序使用一個額外的陣列countArray,其中第i個元素是待排序陣列array中值等於i的元素的個數。然後根據陣列countArray來將array中的元素排到正確的位置。
演算法的步驟如下:
- 找出待排序的陣列中最大和最小的元素
- 統計陣列中每個值為i的元素出現的次數,存入陣列countArray的第i項
- 對所有的計數累加(從countArray中的第一個元素開始,每一項和前一項相加)
- 反向填充目標陣列:將每個元素i放在新陣列的第countArray[i]項,每放一個元素就將countArray[i]減去1
穩定性和複雜度:
計數排序是穩定的排序演算法;平均時間複雜度、最優時間複雜度和最差時間複雜度都為O(n+k),空間複雜度為O(n+k),其中,n為待排元素個數,k為待排元素的範圍(0~k)。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <assert.h> #define RANDMAX 1000000 #define RANDMIN 900000 void getRandArray(int array[], int size); void countSort(int array[], int size); void isSorted(int array[], int size); int main(int argc, char const *argv[]) { int size = 0; scanf("%d", &size); assert(size > 0); int *array = (int *)calloc(size, sizeof(int)); getRandArray(array, size); clock_t begin; clock_t end; begin = clock(); countSort(array, size); end = clock(); //列印排序所花費的時間,在linux下單位為ms printf("%ld\n", (end - begin) / 1000); isSorted(array, size); free(array); return 0; } //利用偽隨機數填充陣列array,偽隨機數的範圍在RANDMIN~RANDMAX-1之間 void getRandArray(int array[], int size) { assert(array != NULL && size > 0); srand((unsigned) time(NULL)); int i = 0; for (i = 0; i < size; ++i) { array[i] = rand() % (RANDMAX - RANDMIN) + RANDMIN ; } } //從小到大進行排序 void countSort(int array[], int size) { assert(array != NULL && size > 0); //計數陣列,用於統計陣列array中各個不同數出現的次數 //由於陣列array中的數屬於0...RANDMAX-1之間,所以countArray的大小要夠容納RANDMAX個int型的值 int *countArray = (int *) calloc(RANDMAX, sizeof(int)); //用於存放已經有序的數列 int *sortedArray = (int *) calloc(size, sizeof(int)); //統計陣列array中各個不同數出現的次數,迴圈結束後countArray[i]表示數值i在array中出現的次數 int index = 0; for (index = 0; index < size; ++index) { countArray[array[index]]++; } //統計數值比index小的數的個數,迴圈結束之後countArray[i]表示array中小於等於數值i的元素個數 for (index = 1; index < RANDMAX; ++index) { countArray[index] += countArray[index - 1]; } for (index = size - 1; index >= 0; --index) { //因為陣列的起始下標為0,所以要減一 sortedArray[countArray[array[index]] - 1] = array[index]; //這裡減一是為了保證當有多個值為array[index]的元素時,後出現的值為array[index]的元素 //放在後面,也就是為了保證排序的穩定性 --countArray[array[index]]; } memcpy(array, sortedArray, size * sizeof(int)); free(sortedArray); free(countArray); } //判斷陣列array是否已經是有序的 void isSorted(int array[], int size) { assert(array != NULL && size > 0); int unsorted = 0; int i = 0; for (i = 1; i < size; ++i) { if (array[i] < array[i - 1]) { unsorted = 1; break; } } if (unsorted) { printf("the array is unsorted!\n"); } else { printf("the array is sorted!\n"); } }
計數排序是非比較的排序演算法,據說其排序速度要快於任何的比較排序演算法(我還未驗證,但是在排序100000000個10000以內的數時花費為606毫秒,而C語言的qsort函式則為10802毫秒,由此可見一斑),由於計數排序需要一個計數陣列以及一個存放有序數列的陣列,故計數排序對記憶體的要求比較高。
------------------------------------------------------------------更新------------------------------------------------------------------
2014年10月20日
以上的實現有兩個問題:
1、比較耗費記憶體:如果元素值的範圍在9百萬~1千萬之間,上面的實現中countArray的大小需要1千萬,而實際出現的不同值的元素就只有1千萬-9百萬=1百萬,也就是說用上面的實現會導致countArray中有90%的空間沒有被利用,這個是很浪費記憶體空間的!雖然這個例子比較極端但是在具體應用面前還是需要注意!
2、不能出現負數:如果元素值中有負數,就不能用上面的實現方法了,因為counArray的下標是待排陣列的元素值,所以上面的實現不能排序包含負數的數列。
解決方法:
1、第一個問題好解決:只要首先一次遍歷待排陣列,找出最大值和最小值(目前我所知的比較快的方法是時間複雜度為O(1.5n)的方法),然後分配大小為(max-min+1)*sizeof(int)的空間給countArray就行了,至於統計的方法就得改變一點點了:
//從小到大進行排序,節省空間的版本
void countSort2(int array[], int size)
{
assert(array != NULL && size > 0);
int min;
int max;
getMinAndMax(array, size, &min, &max);
// printf("max - min + 1 = %d\n", max - min + 1);
int countArraySize = max - min + 1;
//計數陣列,用於統計陣列array中各個不同數出現的次數
int *countArray = (int *) calloc(countArraySize, sizeof(int));
//用於存放已經有序的數列
int *sortedArray = (int *) calloc(size, sizeof(int));
//統計陣列array中各個不同數出現的次數,迴圈結束後countArray[i]表示數值i+min在array中出現的次數
int index = 0;
for (index = 0; index < size; ++index) {
countArray[array[index] - min]++;
}
//統計數值比index小的數的個數,迴圈結束之後countArray[i]表示array中小於等於數值i+min的元素個數
for (index = 1; index < countArraySize; ++index) {
countArray[index] += countArray[index - 1];
}
for (index = size - 1; index >= 0; --index) {
//因為陣列的起始下標為0,所以要減一
sortedArray[countArray[array[index] - min] - 1] = array[index];
//這裡減一是為了保證當有多個值為array[index]的元素時,後出現的值為array[index]的元素
//放在後面,也就是為了保證排序的穩定性
--countArray[array[index] - min];
}
memcpy(array, sortedArray, size * sizeof(int));
free(sortedArray);
free(countArray);
}
//待優化
void getMinAndMax(int array[], int size, int *min, int *max)
{
assert(array != NULL && size > 0);
if (size == 1) {
*min = *max = array[0];
return ;
}
*min = array[0];
int index;
for (index = 1; index < size; ++index) {
if (array[index] < *min) {
*min = array[index];
}
}
*max = array[0];
for (index = 1; index < size; ++index) {
if (array[index] > *max) {
*max = array[index];
}
}
}
這種方法可以改進在元素值範圍比較小的數列的排序時間(在大部分情況時間較短)和空間複雜度。由於只是為了驗證想法的可行性,就沒有在查詢最值的函式上下功夫優化,等以後有時間了,再進一步優化。
2、第二個問題:剛剛在洗漱的時候忽然想到了一個可行的方法:先一次遍歷待排陣列,找出負數中的最大值和最小值,正數中的最大值和最小值,建立兩個計數陣列,negativeCountArray用來統計待排陣列中各個不同負數的出現個數,positiveCountArray用來統計待排陣列中各個正數出現的個數,先統計總的負數的個數,然後再統計各個不同正數的個數,然後在往sortedArray中放元素的時候對正數和負數區別對待。這樣就能讓計數排序對包含負數的數列進行排序了。
程式碼如下:
//從小到大進行排序,節省空間,可排序負數的版本
void countSort3(int array[], int size)
{
assert(array != NULL && size > 0);
int negativeMin;
int negativeMax;
int positiveMin;
int positiveMax;
_getMinAndMax(array, size, &negativeMin, &negativeMax, &positiveMin, &positiveMax);
int positiveCountArraySize = positiveMax - positiveMin + 1;
int negativeCountArraySize = negativeMax - negativeMin + 1;
//計數陣列,用於統計陣列array中各個不同數出現的次數
int *positiveCountArray = (int *) calloc(positiveCountArraySize, sizeof(int));
int *negativeCountArray = (int *) calloc(negativeCountArraySize, sizeof(int));
//用於存放已經有序的數列
int *sortedArray = (int *) calloc(size, sizeof(int));
//統計陣列array中各個不同的數出現的次數,迴圈結束後positiveCountArray[i]表示數值i+positiveMin>=0在array中出現的次數
//negativeCountArray[i]表示數值i+negativeMin<0在array中出現的次數
int index = 0;
for (index = 0; index < size; ++index) {
if (array[index] < 0) {
negativeCountArray[array[index] - negativeMin]++;
} else {
positiveCountArray[array[index] - positiveMin]++;
}
}
//統計數值比index+negativeMin小的負數的個數,迴圈結束之後negativeCountArray
for (index = 1; index < negativeCountArraySize; ++index) {
negativeCountArray[index] += negativeCountArray[index - 1];
}
int negativeCount = negativeCountArray[index - 1];//負數的總數
//統計數值比index小的非負數數的個數,迴圈結束之後positiveCountArray[i]表示array中小於等於數值i的值為非負數的元素個數
for (index = 1; index < positiveCountArraySize; ++index) {
positiveCountArray[index] += positiveCountArray[index - 1];
}
for (index = size - 1; index >= 0; --index) {
if (array[index] < 0) {
sortedArray[negativeCountArray[array[index] - negativeMin] - 1] = array[index];
--negativeCountArray[array[index] - negativeMin];
} else {
//由於非負數總是比負數來的大,所以在把正數放到sortedArray時要在下標上加上負數的總個數
sortedArray[positiveCountArray[array[index] - positiveMin] - 1 + negativeCount] = array[index];
--positiveCountArray[array[index] - positiveMin];
}
}
memcpy(array, sortedArray, size * sizeof(int));
free(sortedArray);
free(positiveCountArray);
free(negativeCountArray);
}
//待優化
void _getMinAndMax(int array[], int size, int *negativeMin, int *negativeMax, int *positiveMin, int *positiveMax)
{
assert(array != NULL && size > 0);
if (size == 1) {
*negativeMin = *negativeMax = *positiveMin = *positiveMax = array[0];
return ;
}
int firstNegative = 0;
int firstPositive = 0;
int index;
for (index = 0; index < size; ++index) {
if (array[index] < 0) {
firstNegative = array[index];
}
}
for (index = 0; index < size; ++index) {
if (array[index] > 0) {
firstPositive = array[index];
}
}
*negativeMin = firstNegative;
*positiveMin = firstPositive;
for (index = 0; index < size; ++index) {
if (array[index] < 0 && array[index] < *negativeMin) {
*negativeMin = array[index];
}
if (array[index] > 0 && array[index] < *positiveMin) {
*positiveMin = array[index];
}
}
*negativeMax = firstNegative;
*positiveMax = firstPositive;
for (index = 0; index < size; ++index) {
if (array[index] < 0 && array[index] > *negativeMax) {
*negativeMax = array[index];
}
if (array[index] > 0 && array[index] > *positiveMax) {
*positiveMax = array[index];
}
}
}
由於只是為了驗證想法的可行性,就沒有在查詢最值的函式上下功夫優化,等以後有時間了,再進一步優化。
其他八種排序演算法的部落格: