C++編寫經典算法之一:基數排序RadixSort(又稱:桶子法BucketSort)(通俗易懂)

基數排序

  • “基數排序”是數列排序的算法之一。
  • 屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)。
  • 基數,是同一類若干數據的集合,比如基數爲個位數,那麼個位數就是所有個位的數字的集合,通俗來說,你可以簡單的理解爲位數。
  • “基數排序”通常有兩種排序思路,一種是從低位到高位,稱之爲LSD(Least significant digital),相對的,另一種是從高位到低位,稱之爲 MSD (Most significant digital)。雖然看起來都是通過基數來進行排序的,但從低到高和從高到低的方法卻是完全不同的,本文只展示比較簡單的:LSD。

一、算法思路

首先我們要明白,在複數範圍內,所有的數都是由0-9組成的,比較0-9就是我們在求學經歷上,去比較兩個數大小的最直觀的判斷。

在這裏,我們將這十個基本的數字稱之爲“鍵值”。
在這裏插入圖片描述

假設我們有如圖數列。

在這裏插入圖片描述
當基數爲1時,即基數爲個位數時,我們可以將數列中所有元素的個位數找出。

在這裏插入圖片描述

然後通過對比鍵值,將基數所代表的原數據,依次填入到鍵值組中。

在這裏插入圖片描述

最後,我們按鍵值從0到9的,將鍵值組中的數再次排列成一個新的數列。

在這裏插入圖片描述

如果我們利用同位素標記法標記出這次操作裏進行關注的數字,可以發現我們這次操作中只對基數爲1的數進行了排序。

  • 其實通過這一次的操作,並沒有完成排序。但如果繼續觀察,不難發現,我們已經對基數爲1對數進行了部分排序,也就是說,我們可以保證:如果十位數相同,那麼個位數必然十有序的。

在這裏插入圖片描述

然後,我們進行同樣的操作。將基數設置爲10.即十位數,將數列中所有元素的十位數找出。

在這裏插入圖片描述

同樣的,通過對比鍵值,將基數所代表的原數據,依次填入到鍵值組中。

在這裏插入圖片描述

我們按鍵值從0到9的,將鍵值組中的數再次排列成一個新的數列。

在這裏插入圖片描述

利用同位素標記法標記出這次操作裏進行關注的數字,可以發現我們這次操作中只對基數爲10的數進行了排序。

  • 我們不難發現,數列中所有的數字,都已經進行了排序,從而整個數列完成了排序。

整個過程如下:

在這裏插入圖片描述

二、代碼清單及測試結果

#include <iostream>
#include <math.h>
#include <ctime>
template <class T>
/*
 * 獲取數組長度
 */
int getSizeOfArray(T& bs){
    return sizeof(bs)/sizeof(bs[0]);
}

/*
 * 獲取數組中最大的數
 */
int getMaximumOfArray(int *rs,int size){
    int max = rs[0];
    for(int i=size-1;i>0;i--){
        if(max<rs[i]){
            max = rs[i];
        }
    }
    return max;
}

/*
 * 獲取一個數的位數
 */
int getByteOfNumber(int number){
    int byte = 0;
    int consult = -1;//商
    while(consult!=0){
        consult = number/10;
        byte++;
        number = consult;
    }
    return byte;
}

/*
 * 基數排序,利用LSD對數列進行排序
 */
void radixSort(int *rs,int size){
    int radix = 1;//基數初始化
    int max = getMaximumOfArray(rs,size);//最大值
    int byte = getByteOfNumber(max);//最大值位數
    int bucket[size];//初始化桶子(index代表鍵值)
    int count[10];//初始化桶子元素計數器(記錄鍵值行所含的元素個數)
    int remainder[size];//初始化餘數數列
    int startIndex[10];//初始化鍵值的起始位數列

    for(int i=1;i<=byte;i++){
        //桶子元素計數器和鍵值的起始位數列每次使用前進行歸位操作
        for(int j=0;j<10;j++){
            count[j] = 0;
            startIndex[j] = -1;
        }
        //餘數數列每次使用前進行清零操作
        for(int j=0;j<size;j++){
            remainder[j] = 0;
        }

        //計算鍵值行個數
        for(int j=0;j<size;j++){
            int re = (rs[j]/radix)%10;
            remainder[j] = re;
            count[re]++;
        }
        //利用桶子元素計數器計算各鍵值的起始位
        int sum = 0;
        for(int j=0;j<10;j++){
            if(count[j]!=0){
                startIndex[j] = sum;
                sum += count[j];
            }
        }
        //進桶
        for(int j=0;j<size;j++){
            bucket[startIndex[remainder[j]]] = rs[j];
            startIndex[remainder[j]]++;
        }
        //出桶
        for(int j=0;j<size;j++){
            rs[j] = bucket[j];
        }
        //基數擴大
        radix = radix * 10;
    }
}


int main() {
    using namespace std;

    int rs[] = {73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
    int size = getSizeOfArray(rs);

//    clock_t startTime,endTime;

    cout<< "原數列:";

    for(int i = 0;i<size;i++)
    {
        cout<< rs[i] << " ";
    }

    cout<< "\n" << "基數排序後:";

//    startTime = clock();
    radixSort(rs,size);
//    endTime = clock();

    for(int i = 0;i<size;i++)
    {
        cout<< rs[i] << " ";
    }

//    cout << "\n"<<"The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;

    return 0;
}

在這裏插入圖片描述

三、算法分析

“時間效率 :設待排序列爲n個記錄,d個關鍵碼,關鍵碼的取值範圍爲radix,則進行鏈式基數排序的時間複雜度爲O(d(n+radix)),其中,一趟分配時間複雜度爲O(n),一趟收集時間複雜度爲O(radix),共進行d趟分配和收集。 空間效率:需要2*radix個指向隊列的輔助空間,以及用於靜態鏈表的n個指針。”

上述分析來自引用:

https://www.cs.auckland.ac.nz/software/AlgAnim/radixsort.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章