1. 程式人生 > >基數排序及其C語言實現

基數排序及其C語言實現

基數排序不同於之前所介紹的各類排序,前邊介紹到的排序方法或多或少的是通過使用比較和移動記錄來實現排序,而基數排序的實現不需要進行對關鍵字的比較,只需要對關鍵字進行“分配”“收集”兩種操作即可完成。

例如對無序表{50,123,543,187,49,30,0,2,11,100}進行基數排序,由於每個關鍵字都是整數數值,且其中的最大值由個位、十位和百位構成,每個數位上的數字從 0 到 9,首先將各個關鍵字按照其個位數字的不同進行分配分配表如下圖所示:

  通過按照各關鍵字的個位數進行分配,按照順序收集得到的序列變為:{50,30,0,100,11,2,123,543,187,49}。在該序列表的基礎上,再按照各關鍵字的十位對各關鍵字進行分配,得到的分配表如下圖所示:

  由上表順序收集得到的記錄表為:{0、100、2、11、123、30、543、49、50、187}。在該無序表的基礎上,依次將表中的記錄按照其關鍵字的百位進行分配,得到的分配如下圖所示:

  最終通過三次分配與收集,最終得到的就是一個排好序的有序表:{0、2、11、30、49、50、100、123、187、543}

例子中是按照個位-十位-百位的順序進行基數排序,此種方式是從最低位開始排序,所以被稱為最低位優先法(簡稱“LSD法”)

同樣還可以按照百位-十位-各位的順序進行排序,稱為最高位優先法(簡稱“MSD法”),使用該方式進行排序同最低位優先法不同的是:當無序表中的關鍵字進行分配後,相當於進入了多個子序列,後序的排序工作分別在各個子序列中進行(最低位優先法每次分配與收集都是相對於整個序列表而言的)。

例如還是對{50,123,543,187,49,30,0,2,11,100}
使用最高位優先法進行排序,首先按照百位的不同進行分配,得到的分配表為:

  由上圖所示,整個無序表被分為了 3 個子序列,序列 1 和序列 2 中含有多個關鍵字,序列 3 中只包含了一個關鍵字,最高位優先法完成排序的標準為:直到每個子序列中只有一個關鍵字為止,所以需要分別對兩個子序列進行再分配,各自的分配表如下圖所示:

  上表中,序列 1 中還有含有兩個關鍵字的子序列,所以還需要根據個位進行分配,最終按照各子序列的順序同樣會得到一個有序表。

基數排序的具體實現(採用LSD法)

基數排序較宜使用鏈式儲存結構作為儲存結構,相比於順序儲存結構更節省排序過程中所需要的儲存空間,稱之為“鏈式基數排序”


其具體的實現程式碼為:
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_OF_KEY 8//構成關鍵字的組成部分的最大個數
#define RADIX 10        //基數,例如關鍵字是數字,無疑由0~9組成,基數就是10;如果關鍵字是字串(字母組成),基數就是 26
#define MAX_SPACE 10000
//靜態連結串列的結點結構
typedef struct{
    int data;//儲存的關鍵字
    int keys[MAX_NUM_OF_KEY];//儲存關鍵字的陣列(此時是一位一位的儲存在陣列中)
    int next;//做指標用,用於是靜態連結串列,所以每個結點中儲存著下一個結點所在陣列中的位置下標
}SLCell;
//靜態連結串列結構
typedef struct{
    SLCell r[MAX_SPACE];//靜態連結串列的可利用空間,其中r[0]為頭結點
    int keynum;//當前所有的關鍵字中最大的關鍵字所包含的位數,例如最大關鍵字是百,說明所有keynum=3
    int recnum;//靜態連結串列的當前長度
} SLList;

typedef int  ArrType[RADIX];//指標陣列,用於記錄各子序列的首尾位置
//排序的分配演算法,i表示按照分配的位次(是個位,十位還是百位),f表示各子序列中第一個記錄和最後一個記錄的位置
void Distribute(SLCell *r,int i,ArrType f,ArrType e){
    //初始化指標陣列
    for (int j=0; j<RADIX; j++) {
        f[j]=0;
    }
    //遍歷各個關鍵字
    for (int p=r[0].next; p; p=r[p].next) {
        int j=r[p].keys[i];//取出每個關鍵字的第 i 位,由於採用的是最低位優先法,所以,例如,第 1 位指的就是每個關鍵字的個位
        if (!f[j]) {//如果只想該位數字的指標不存在,說明這是第一個關鍵字,直接記錄該關鍵字的位置即可
            f[j]=p;
        }else{//如果存在,說明之前已經有同該關鍵字相同位的記錄,所以需要將其進行連線,將最後一個相同的關鍵字的next指標指向該關鍵字所在的位置,同時最後移動尾指標的位置。
            r[e[j]].next=p;
        }
        e[j]=p;//移動尾指標的位置
    }
}
//基數排序的收集演算法,即重新設定連結串列中各結點的尾指標
void Collect(SLCell *r,int i,ArrType f,ArrType e){
    int j;
    //從 0 開始遍歷,查詢頭指標不為空的情況,為空表明該位沒有該型別的關鍵字
    for (j=0;!f[j]; j++);
    r[0].next=f[j];//重新設定頭結點
    int t=e[j];//找到尾指標的位置
    while (j<RADIX) {
        for (j++; j<RADIX; j++) {
            if (f[j]) {
                r[t].next=f[j];//重新連線下一個位次的首個關鍵字
                t=e[j];//t代表下一個位次的尾指標所在的位置
            }
        }
    }
    r[t].next=0;//0表示連結串列結束
}
void RadixSort(SLList *L){
    ArrType f,e;
    //根據記錄中所包含的關鍵字的最大位數,一位一位的進行分配與收集
    for (int i=0; i<L->keynum; i++) {
        //秉著先分配後收集的順序
        Distribute(L->r, i, f, e);
        Collect(L->r, i, f, e);
    }
}
//建立靜態連結串列
void creatList(SLList * L){
    int key,i=1,j;
    scanf("%d",&key);
    while (key!=-1) {
        L->r[i].data=key;
        for (j=0; j<=L->keynum; j++) {
            L->r[i].keys[j]=key%10;
            key/=10;
        }
        L->r[i-1].next=i;
        i++;
        scanf("%d",&key);
    }
    L->recnum=i-1;
    L->r[L->recnum].next=0;
}
//輸出靜態連結串列
void print(SLList*L){
    for (int p=L->r[0].next; p; p=L->r[p].next) {
        printf("%d ",L->r[p].data);
    }
    printf("\n");
}
int main(int argc, const char * argv[]) {
    SLList *L=(SLList*)malloc(sizeof(SLList));
    L->keynum=3;
    L->recnum=0;
    creatList(L);//建立靜態連結串列
    printf("排序前:");
    print(L);
   
    RadixSort(L);//對靜態連結串列中的記錄進行基數排序
   
    printf("排序後:");
    print(L);
    return 0;
}
執行結果為: 50
123
543
187
49
30
0
2
11
100
-1
排序前:50 123 543 187 49 30 0 2 11 100
排序後:0 2 11 30 49 50 100 123 187 543