排序算法的C語言實現(下 線性時間排序:計數排序與基數排序)


計數排序

計數排序是一種高效的線性排序

通過計算一個集合中元素出現的次數來確定集合如何排序。不同於插入排序、快速排序等基於元素比較的排序,計數排序是不需要進行元素比較的,而且它的運行效率要比效率為O(nlgn)的比較排序高。

計數排序有一定的局限性,其中最大的局限就是它只能用於整型或那么可以用整型來表示的數據集合。原因是計數排序利用一個數據的索引來記錄元素出現的次數,而這個數組的索引就是元素的數值。例如,如果整數3出現過4次,那么4將存儲到數組索引為3的位置上。同時,我們還需要知道集合中最大整數的值,以便於為數組分配足夠的空間。

除了速度之外,計數排序的另一個優點就是非常穩定穩定的排序能使具有相同數值的元素具有相同的順序,就像它們在原始集合中表現出來的一樣。在某些情況下這是一個重要的特性,可以在基數排序中看到這一點。

 計數排序的接口定義

ctsort


int ctsort(int *data, int size, int k);

返回值:如果排序成功,返回0;否則,返回-1;

描述:   利用計數排序將數組data中的整數進行排序。data中的元素個數由size決定。參數k為data中最大的整數加1。當ctsort返回時,data中包含已經排序的元素。

復雜度:O(n+k),n為要排序的元素個數,k為data中最大的整數加1。

計數排序的實現與分析

計數排序本質上是通過計算無序集合中整數出現的次數來決定集合應該如何排序的

在以下說明的實現方法中,data初始包含size個無序整型元素,並存放在單塊連續的存儲空間中。另外需要分配存儲空間來臨時存放已經排序的元素。在ctsort返回時,得到的有序集合將拷貝加data。

分配了存儲空間以后,首先計算data中每個元素出現的次數。這些結果將存儲到計數數組counts中,並且數組的索引值就是元素本身。一旦data中每個元素的出現次數都統計出來后,就要調整計數值,使元素在進入有序集合之前,清楚每個元素插入的次數。用元素本身的次數加上它前一個元素的次數。事實上,此時counts包含每個元素在有序集合temp中的偏移量

要完成排序,還必須按照元素在temp中的偏移量放置元素。當temp更新時,每個元素的計數要減1,這樣,在data中出現不止一次的元素在temp中也會出現不止一次,這樣保持同步。

計數排序的時間復雜度為O(n+k),其中n為要排序的元素個數,k為data中最大的整數加1。這是由於計數排序包含三個循環,其中兩個的運行時間正比於n,另一個的運行時間正比於k。對於空間上來說,計數排序需要兩個大小為n的數組,一個大小為k的數組。

示例:計數排序的實現

/*ctsort.c*/
#include <stdlib.h>
#include <string.h>
#include "sort.h"

/*ctsort  計數排序函數*/
int ctsort(int *data, int size, int k)
{
    int *counts,
        *temp;
        
    int i,j;
    
    /*為計數器數組分配空間*/
    if((counts = (int *)malloc(k * sizeof(int))) == NULL)
        return -1;
    
    /*為已排序元素臨時存放數組分配空間*/
    if((temp = (int *)malloc(size * sizeof(int))) == NULL)
        return -1;
    
    /*初始化計數數組*/
    for(i = 0; i < k; i++)
    {
        counts[i] = 0;        
    }
    
    /*統計每個元素出現的次數(counts的下標索引即是要統計的元素本身)*/
    for(j=0; j<size; j++)
        counts[data[j]]=counts[data[j]] + 1;
    
    /*將元素本身的次數加上它前一個元素的次數(得到元素偏移量)*/
    for(i = 1; i < k; i++)
        counts[i]=counts[i] + counts[i-1];
    
    /*關鍵代碼:使用上面得到的計數數組去放置每個元素要排序的位置*/
    for(j = size -1; j >= 0; j--)
    {
        temp[counts[data[j]]-1] = data[j];      /*counts的值是元素要放置到temp中的偏移量*/
        counts[data[j]] = counts[data[j]] - 1;  /*counts的計數減1*/
    }
    
    /*將ctsort已排序的元素從temp拷貝回data*/
    memcpy(data,temp,size * sizeof(int));
    
    /*釋放前面分配的空間*/
    free(counts);
    free(temp);
    
    return 0;
}

基數排序

基數排序是另外一種高效的線性排序算法

其方法是將數據按位分開,並從數據的最低有效位到最高有效位進行比較,依次排序,從而得到有序數據集合

我們來看一個例子,用基數排序對十進制數據{15,12,49,16,36,40}進行排序。在對個位進行排序之后,其結果為{40,12,15,16,36,49},在對十位進行排序之后,其結果為{12,15,16,36,40,49}。

有一點非常重要,在對每一位進行排序時其排序過程必須是穩定的一旦一個數值通過較低有效位的值進行排序之后,此數據的位置不應該改變,除非通過較高有效位的值進行比較后需要調整它的位置。例如,在上述的例子中,12和15的十位都是1,當對其十位進行排序時,一個不穩定的排序算法可能不會維持其在個數排序過程中的順序。而一個穩定的排序算法可以保證它們不重新排序。基數排序會用到計數排序,對於基數排序來說,除了穩定性,它還是一種線性算法,且必須知道每一位可能的最大整數值

基數排序並不局限於對整數進行排序,只要能把元素分割成整型數,就可以使用基數排序。例如,可以對一個以28為基數字符串進行基數排序;或者,可以對一個64位的整數,按4位以216為基數的值進行排序。具體該選擇什么值作為基數取決於數據本身,同時考慮到空間的限制,需要將pn+pk最小化。(其中p為每個元素的位數,n為元素的個數,k為基數)。一般情況下,通常使k小於等於n。

基數排序的接口定義

rxsort


int rxsort(int *data, int size, int p, int k);

返回值:如果排序成功,返回0;否則返回-1。

描述:利用計數排序將數組data中的整數進行排序。數組data中整數的個數由size決定。參數p指定每個整數包含的位數,k指定基數。當rxsort返回時,data包含已經排序的整數。

復雜度:O(pn+pk),n為要排序的元素個數,k為基數,p為位的個數。

基數排序的實現與分析

基數排序實質上是在元素每一位上應用計數排序來對數據集合排序。在以下介紹的實現方法中,data初始包含size個無序整型元素,並存放在單塊連續的存儲空間中。當rxsort返回時,data中的數據集完全有序。

如果我們理解了計數排序的方法,那么基數排序也就非常簡單了。單個循環控制正在進行排序的位置。從最低位開始一個位置一個位置地應用計數排序來不斷調整元素。一旦調整完了最高有效位的數值,排序過程就完成了。

獲取每位數值的簡單方法就是使用冪運算和模運算。這對整數來說特別有效,但不同的數據類型需要使用不同的方法。有一些方法可能需要考慮機器具體細節,例如字節順序和字對齊等。

毫無疑問,基數排序的時間復雜度取決於它選擇哪種穩定排序來對數值進行排序。由於基數排序對每個p位置的位數值使用計數排序,因此基數排序消耗的運行時間是計數排序的p倍,即O(pn+pk)。其對空間的要求與計數排序一樣:兩個大小為n的數組,一個大小為k的數組。

示例:基數排序的實現

/*rxsort.c*/
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "sort.h"

/*rxsort*/
int rxsort(int *data, int size, int p, int k)
{
    int *counts, *temp;
    int index, pval, i, j, n;
    
    /*為計數器數組分配空間*/
    if((counts = (int *)malloc(k * sizeof(int))) == NULL)
        return -1;
    /*為已排序元素集分配空間*/
    if((temp = (int *)malloc(size * sizeof(int))) == NULL)
        return -1;
    
    /*從元素的最低位到最高位開始排序*/
    for(n=0; n<p; n++)
    {
        /*初始化計數器*/
        for(i=0; i<k; i++)
            count[i] = 0;
        /*計算位置值(冪運算k的n次方)*/
        pval = (int)pow((double)k,(double)n);
        
        /*統計當前位上每個數值出現的次數*/
        for(j=0; j<size; j++)
        {
            index = (int)(data[j] / pval) % k;
            counts[index] = counts[index]+1;
        }
        /*計算偏移量(本身的次數加上前一個元素次數)*/
        for(i=1; i<k; i++)
            counts[i] = counts[i] + counts[i-1];
        
        /*使用計數器放置元素位置*/
        for(j=size-1; j>=0; j--)
        {
            index = (int)(data[j] / pval) % k;
            temp[counts[index]-1] = data[j];
            counts[index] = counts[index] - 1;
        }
        
        /*將已排序元素拷貝回data*/
        memcpy(data, temp, size*sizeof(int));
        
    }
    
    /*釋放已排序空間*/
    free(counts);
    free(temp);
    
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM