一天一演算法 - 基數排序

weixin_34337265發表於2017-03-10

介紹

  1. 比較型排序和非比較型排序

首先,比較型排序和非比較型排序有何不同呢?很簡單,如果在排序過程中,需要比較陣列中的元素大小,然後將元素放置在最終位置,這稱之為比較型排序。舉個簡單的例子來說,對於選擇排序,排序的思想就是從第一個位置開始,找到陣列中最小的元素放進去,那麼怎麼找到最小的元素呢?這個時候就需要設定一個 min 變數,在陣列中依次比較,不斷地替換 min(如果元素的值小於 min ) 直到陣列最後的一個元素。

一個比較型排序的比較次數直接影響演算法的效能,比如選擇排序的比較次數就與 (N^2) 成正比。

非比較型排序不需要經過比較就可以將一個陣列變成有序的,從理論上來說,它應該快於任何比較型演算法,可事實上卻不是這樣!但是值得注意的是,比較型排序演算法的時間複雜度是不可能會突破 O(NlogN) 的,但是非比較型演算法比如基數排序在某些情況下卻可以突破下界,達到 O(N)

  1. 穩定排序和不穩定排序

穩定與不穩定該怎麼鑑定呢?陣列中有任意兩個元素 a[i]a[j], 而且 a[i]a[j] 滿足條件 a[i] == a[j] && i > j,如果在排序後,i > j 仍然成立,那麼這種排序就是穩定排序,否則是不穩定排序。

基數排序是一種穩定排序演算法。


演算法思想

將所有待比較的數值統一為相同位數,長度不夠的在高位補0,然後從低位開始依次進行排序,這樣等排到最高位的時候,整個陣列自然就是有序的了。

前面我們說了,基數排序是一種非比較型排序,那麼我們依賴怎樣的手段對資料進行排序呢?

不論個位,十位還是百位,其數值都在 [0, 9] 這個區間內,所以我們首先宣告一個陣列 backet[10],然後再將 bucket[0] ... bucket[9] 宣告為陣列,這樣的話,我們就可以對個位進行排序,因為個位為 0 的都被放在 bucket[0] 這個陣列中,個位為 1 的都被放在 bucket[1] 這個陣列裡...

當進行完個位的排序時,按照順序將 bucket[] 元素重新推入原陣列中,然後將 bucket 陣列清空,以便進行十位的排序。

如果看了以上文字你還是渾渾噩噩的,那麼就來舉個簡單的例子,比如我們對以下陣列進行排序。

[1, 21, 89, 110, 56]

首先,我們宣告一個陣列。

var bucket = [];
for(var i = 0; i < 10; i++) {
    bucket[i] = [];     
}

[1, 21, 89, 110, 56] 的個位分別是 [1, 1, 9, 0, 6], 很顯然,對個位排序後,bucket[0] = [110], bucket[1] = [1, 21], bucket[6] = [56], bucket[9] = [89], 將這些數依次推入原陣列,這個時候陣列為 [110, 1, 21, 56, 89]

[110, 1, 21, 56, 89] 的十位分別為 [1, 0, 2, 5, 8],對十位進行排序後,bucket[0] = [1], bucket[1] = [110], bucket[2] = [21], bucket[5] = [56], bucket[8] = [89], 將這些數依次推入原陣列,這個時候陣列為 [1, 110, 21, 56, 89]

[1, 110, 21, 56, 89] 的百位分別是 [0, 1, 0, 0, 0],對百位進行排序後,bucket[0] = [1, 21, 56, 89], bucket[1] = [110],將這些數依次推入原陣列,這個時候陣列為 [1,21,56,89,110]

可見,這個時候陣列就變得有序了,而且和比較型排序不同的是,我們並沒有與陣列中的其它元素進行比較,只是根據自己各個位數的值讓其放進不同的陣列中。


實現 (JavaScript)

/*
 * @Author: hwaphon
 * @Date:   2017-03-10 13:07:02
 * @Last Modified by:   hwaphon
 * @Last Modified time: 2017-03-10 13:34:52
 */

function radixSort(array) {

    if (array.length <= 1) {
        return array;
    }

    var length = array.length,
        i,
        j,
        str,
        k,
        t,
        loop,
        bucket = [],
        max = array[0];

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

    loop = (max + '').length;

    for (i = 0; i < 10; i++) {
        bucket[i] = [];
    }

    for (i = 0; i < loop; i++) {
        for (j = 0; j < length; j++) {
            str = array[j] + '';

            if (str.length - 1 >= i) {
                k = parseInt(str[str.length - i - 1]);
                bucket[k].push(array[j]);
            } else {
                bucket[0].push(array[j]);
            }
        }

        array.splice(0, length);

        for (j = 0; j < 10; j++) {
            t = bucket[j].length;
            for (k = 0; k < t; k++) {
                array.push(bucket[j][k]);
            }

            bucket[j] = [];
        }
    }

    return array;
}

總結

剛才我們介紹的基數排序是從低位開始進行排序的,這種排序稱之為最低位優先排序(LSD), 還有一種最高位優先排序(MSD)。LSD的基數排序適用於位數小的數列,如果位數多的話,使用MSD的效率會比較好。

我在我電腦上對比了基數排序與快速排序的效率,無論我選擇怎樣的資料,總是快速排序更快一點,如果看到我部落格的人知道原因或者知道基數排序適用情況請告與我知曉,感激不盡。

還有一點,因為快速排序是原地排序,而基數排序還需要額外的記憶體消耗,所以在選擇排序演算法時一定要考慮這一點!

擴充閱讀:

  1. 《穩定排序和不穩定排序》

  2. 《常見的排序演算法 - 基數排序》

相關文章