經典排序算法系列9----分配排序(桶排序和基數排序)

桶排序和基數排序均屬于分配排序。分配排序的基本思想:排序過程無須比較關鍵字,而是通過用額外的空間來"分配"和"收集"來實現排序,它們的時間復雜度可達到線性階:O(n)。簡言之就是:用空間換時間,所以性能與基于比較的排序才有數量級的提高!

桶排序(Bucket Sort),也稱箱排序
基本思想:設置若干個箱子,依次掃描待排序的記錄 array[0],array[1],…,array[n - 1],把關鍵字等于 k 的記錄全都裝入到第 k 個箱子里(分配),然后按序號依次將各非空的箱子里的記錄收集起來,從而完成排序。

桶排序所需要的額外空間取決于關鍵字的個數,若 array[0..n - 1] 中關鍵字的取值范圍是 0 到 m - 1 的整數,則必須設置 m 個箱子。因此箱排序要求關鍵字的類型是有限類型,否則可能要無限個箱子。一般情況下每個箱子中存放多少個關鍵字相同的記錄是無法預料的,故箱子的類型應設計成鏈表為宜。

在實現上,桶排序一般采用另外的變種。即:把 [0, m) 劃分為 m 個大小相同的子區間,每一子區間是一個桶。然后將 n 個記錄分配到各個桶中。因為關鍵字序列是均勻分布在[0,m)上的,所以一般不會有很多個記錄落入同一個桶中。由于同一桶中的記錄其關鍵字不盡相同,所以必須采用關鍵字比較的排序方法(通常用插入排序)對各個桶進行排序,然后依次將各非空桶中的記錄收集起來即可。

C 代碼實現:

struct bucket_node {
    int key;
   bucket_node* next;
};
// 取得數組中最大數的位數
int get_max_digital_count(int* array, int length)
{
    assert(array && length > 0);

    int i = 0;
    int max = array[0];
    int maxDigitalCount = 0;

    for (i = 1; i < length; i++) {
        if (max < array[i]) {
            max = array[i];
        }
    }

  while(max > 0)
  {
    max /= 10;
    ++maxDigitalCount;
  }

    return maxDigitalCount;
}

// 取得數 num 中從低到高第 n 位上的數字
int get_ditital_at(int num, int n)
{
    while (--n > 0) {
        num /= 10;
    }

    return (num % 10);
}

// 箱/桶排序
//
void bucket_sort(int* array, int length)
{
    assert(array && length >= 0);

    if (length <= 1) {
        return;
    }

    int i, index;
    bucket_node* temp = NULL;
    bucket_node bucket[10] = {0, };    // 根據數字個數 0 ~ 9 建立 10 個桶

    int count = get_max_digital_count(array, length);

    // 建立數據節點
    bucket_node* data = (bucket_node*)malloc(length * sizeof(bucket_node));
    if (!data) {
        printf("Error: out of memory!/n");
        return;
    }

    for (i = 0; i < length; i++) {
        data[i].key = array[i];
        data[i].next = NULL;
    }

    // 分配
    for (i = 0; i < length; i++) {
        index = get_ditital_at(data[i].key, count);
        if (bucket[index].next == NULL) {
            bucket[index].next = &data[i];
        }
        else {
            temp = &bucket[index];
            while (temp->next != NULL && temp->next->key < data[i].key) {
                temp = temp->next;
            }

            data[i].next = temp->next;
            temp->next = &data[i];
        }
    }

    // 收集
    index = 0;
    for (i = 0; i < 10; i++) {
        temp = bucket[i].next;
        while (temp != NULL) {
            array[index++] = temp->key;
            temp = temp->next;
        }
    }


    free(data);
}

時間復雜度分析:桶排序的平均時間復雜度是線性的,即 O(n)。但最壞情況仍有可能是 O(n ^ 2)。

空間復雜度分析:桶排序只適用于關鍵字取值范圍較小的情況,否則所需箱子的數目 m 太多導致浪費存儲空間和計算時間。

基數排序(Radix Sort)基本思想:基數排序是對桶排序的改進和推廣。如果說桶排序是一維的基于桶的排序,那么基數排序就是多維的基于桶的排序。我這么說,可能還不是太清楚。比方說:用桶排序對 [0, 30] 之間的數進行排序,那么需要 31 個桶,分配一次,收集一次,完成排序;那么基數排序則只需要 0 - 9 總共 10 個桶(即關鍵字為數字 0 - 9),依次進行個位和十位的分配和收集從而完成排序。

C 代碼實現:

 // 基數排序
//
void radix_sort(int* array, int length)
{
    assert(array && length >= 0);

    if (length <= 1) {
        return;
    }

    const int buffer_size = length * sizeof(int);

    int i, k, count, index;
    int bucket[10] = {0, };    // 根據數字個數 0 ~ 9 建立 10 個桶

    int* temp = (int*)malloc(buffer_size);
    if (!temp) {
        printf("Error: out of memory!/n");
        return;
    }

    count = get_max_digital_count(array, length);

    for (k = 1; k <= count; ++k) {
        memset(bucket, 0, 10 * sizeof(int));

        // 統計各桶中元素的個數
        for (i = 0; i < length; ++i) {
            index = get_ditital_at(array[i], k);
            ++bucket[index];
        }

        // 為每個記錄創建索引下標
        for (i = 1; i < 10; ++i) {
            bucket[i] += bucket[i - 1];
        }

        // 按索引下標順序排列
        for (i = length - 1; i >= 0; --i) {
            index = get_ditital_at(array[i], k);
            assert(bucket[index] - 1 >= 0);
            temp[--bucket[index]] = array[i];
        }

        // 一趟桶排序完畢,拷貝結果
        memcpy(array, temp, buffer_size);

#ifdef DEBUG_SORT
        debug_print(" 第 %d 趟排序:", k);
        for (i = 0; i < length; ++i) {
            debug_print("%d ", array[i]);
        }

        debug_print("/n");
#endif
    }

    free(temp);
}

時間復雜度分析:基數排序的時間負責度為 O(n)。

空間復雜度:基數排序所需的輔助存儲空間為 O(n + r * d),其中 r 為記錄中關鍵字分量的最大個數,d 為關鍵字的個數。比如說:待排序為 0 - 999,那么分量的最大個數為 3,關鍵字的個數為 10(0 - 9)。

補充:基數排序是穩定的。

若排序文件不是以數組 array 形式給出,而是以單鏈表形式給出(此時稱為鏈式的基數排序),則可通過修改出隊和入隊函數使表示箱子的鏈隊列無須分配結點空間,而使用原鏈表的結點空間。人隊出隊操作亦無需移動記錄而僅需修改指針。雖然這樣一來節省了一定的時間和空間,但算法要復雜得多,且時空復雜度就其數量級而言并未得到改觀。


測試:在前文《排序算法之插入排序》測試代碼的基礎上添加兩行代碼即可:
{"桶/箱排序", bucket_sort},
{"基數排序", radix_sort},

測試結果:
=== 桶/箱排序 ===
original: 65 32 49 10 8 72 27 42 18 58 91
sorted: 8 10 18 27 32 42 49 58 65 72 91

original: 10 9 8 7 6 5 4 3 2 1 0
sorted: 0 1 2 3 4 5 6 7 8 9 10

=== 基數排序 ===
original: 65 32 49 10 8 72 27 42 18 58 91
第 1 趟排序:10 91 32 72 42 65 27 8 18 58 49
第 2 趟排序:8 10 18 27 32 42 49 58 65 72 91
sorted: 8 10 18 27 32 42 49 58 65 72 91

original: 10 9 8 7 6 5 4 3 2 1 0
第 1 趟排序:10 0 1 2 3 4 5 6 7 8 9
第 2 趟排序:0 1 2 3 4 5 6 7 8 9 10
sorted: 0 1 2 3 4 5 6 7 8 9 10


推薦閱讀:
經典排序算法系列1----冒泡排序的實現
經典排序算法系列2----插入排序的實現
經典排序算法系列3----直接選擇排序及交換二個數據的正確實現
經典排序算法系列4----希爾排序的實現
經典排序算法系列5----歸并排序
經典排序算法系列6----快速排序
經典排序算法系列7----堆與堆排序
經典排序算法系列8----7大排序算法總結篇
經典排序算法系列9----分配排序(桶排序和基數排序)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,209評論 0 52
  • 概述排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的...
    Luc_閱讀 2,287評論 0 35
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,739評論 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個記錄插入到已排序好...
    依依玖玥閱讀 1,266評論 0 2
  • (本文參加#感悟三下鄉,青春筑夢行#活動。本人承諾,本文是原創,且并未在其他平臺上發表過。) 暑期社會實踐活動是我...
    溪影閱讀 178評論 0 0