1. 程式人生 > >計數排序(C語言版)

計數排序(C語言版)

先說說計數排序的思想

計數排序假定待排序的所有元素都是介於0到K之間的整數;計數排序使用一個額外的陣列countArray,其中第i個元素是待排序陣列array中值等於i的元素的個數。然後根據陣列countArray來將array中的元素排到正確的位置。

 演算法的步驟如下:

  1. 找出待排序的陣列中最大和最小的元素
  2. 統計陣列中每個值為i的元素出現的次數,存入陣列countArray的第i
  3. 對所有的計數累加(從countArray中的第一個元素開始,每一項和前一項相加)
  4. 反向填充目標陣列:將每個元素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];
        }
    }
}

由於只是為了驗證想法的可行性,就沒有在查詢最值的函式上下功夫優化,等以後有時間了,再進一步優化。

其他八種排序演算法的部落格: