1. 程式人生 > >桶排序(箱排序)原理及其時間複雜度詳解

桶排序(箱排序)原理及其時間複雜度詳解

排序充斥著我們的生活,比如站隊、排隊買票、考試排名、公司業績排名、將電子郵件按時間排序、QQ 好友列表中的會員紅名靠前,等等。

這裡先舉個例子,通過這個例子讓我們接觸第 1 個演算法。

在某個期末考試中,老師要把大家的分數排序,比如有 5 個學生,分別考 5、9、5、1、6 分(滿分 10 分),從大到小排序應該是 9、6、5、5、1,大家有沒有辦法寫一段程式隨機讀取 5 個數,然後對它們排序呢?

看到這個問題,我們用 5 分鐘想一下該怎麼辦。辦法當然很多,這裡使用桶排序的思想來處理。

我們找到 11 個桶,分別編號為 0-10,對應 0-10 分,如圖 1 所示。


圖 1 準備 11 個桶並編號
接著我們把這些分數按照桶的編號放入桶中,如圖 2 所示。


圖 2 把分數都放入桶中
接著我們從最大編號的桶到最小編號的桶依次輸出每個桶中的分數,分別是 9、6、5、5、1 了。是不是很輕鬆地完成排序了呢?這就是桶排序的思想。

什麼是桶排序

桶排序,也叫作箱排序,是一個排序演算法,也是所有排序演算法中最快、最簡單的排序演算法。其中的思想是我們首先需要知道所有待排序元素的範圍,然後需要有在這個範圍內的同樣數量的桶,接著把元素放到對應的桶中,最後按順序輸出。

這實際上是簡易版的桶排序,我們想象一下,如果考試分數的範圍是 0~100 萬該怎麼辦?弄 100 萬個桶嗎?

實際上在這種情況下,一個桶並不總是放同一個元素,在很多時候一個桶裡可能會放多個元素,這是不是與散列表有點相似呢?其實真正的桶排序和散列表有一樣的原理。

除了對一個桶內的元素做連結串列儲存,我們也有可能對每個桶中的元素繼續使用其他排序演算法進行排序,所以更多時候,桶排序會結合其他排序演算法一起使用。

桶排序的實現

我們怎麼在程式碼中實現桶排序呢?其實很簡單,使用陣列就好了。比如有 11 個桶,我們只需要宣告一個長度為 11 的陣列,然後每把一個元素往桶中放時,就把陣列指定位置的值加 1,最終倒序輸出陣列的下標,陣列每個位置的值為幾就輸出幾次下標,這樣就可以實現桶排序了。

下面我們一起看看桶排序的程式碼。
package me.irfen.algorithm.ch03;
public class BucketSort {
    private int[] buckets;
    private int[] array;

    public BucketSort(int range, int[] array) {
        this.buckets = new int[range];
        this.array = array;
    }

    /**
     * 排序
     */
    public void sort() {
        if (array != null && array.length > 1) {
            for (int i = 0; i < array.length; i++) {
                buckets[array[i]] ++;
            }
        }
    }

    /**
     * 從大到小排序
     */
    public void print() {
        // 倒序輸出資料
        for (int i = buckets.length - 1; i >= 0; i--) {
            // 元素中值為幾,說明有多少個相同值的元素,則輸出幾遍
            for (int j = 0; j < buckets[i]; j ++) {
                System.out.println(i);
            }
        }
    }
}
下面來看看呼叫的測試程式碼。
package me.irfen.algorithm.ch03;
public class SortTest{
    public static void main(String[] args){
        testBucketSort();
    }
    //桶排序
    private static void testBucketSort(){
        int [] array = {5,9,1,9,5,3,7,6,1};
        BucketSort bucketSort = new BucketSort(11,array);
        bucketSort.sort();
        bucketSoet.print();
    }
}

桶排序的時間複雜度

桶排序實際上只需要遍歷一遍所有的待排序元素,然後依次放入指定的位置。如果加上輸出排序的時間,那麼需要遍歷所有的桶,時間複雜度就是 O(n+m),其中,n 為待排序的元素的個數,m 為桶的個數。這是相當快速的排序演算法,但是對於空間的消耗來說有點太大了。

比如我們對 1、10、100、1000 這四個元素排序,那麼我們需要長度為 1001 的陣列用來排序,如果是對 1、1000、100000 排序呢?我們發現,當元素的跨度範圍越大時,空間的浪費就越大,即使只有幾個元素,但是這個範圍才是空間的大小。所以桶排序的空間複雜度是 O(m),其中 m 為桶的個數,待排序元素分佈越均勻,也就是說當元素能夠非常均勻地填滿所有的桶時,這個空間的利用率是最好的。不過這種情況並不多見,在多數情況下,資料並不會均勻地分佈。

通過上面的效能分析,我們可以知道桶排序的特點,那就是速度快、簡單,但是也有相應的弱點,那就是空間利用率低,如果資料跨度過大,則空間可能無法承受,或者說這些元素並不適合使用桶排序演算法。

桶排序的適用場景

桶排序的適用場景非常明瞭,那就是在資料分佈相對比較均勻或者資料跨度範圍並不是很大時,排序的速度還是相當快且簡單的。

但是當資料跨度過大時,這個空間消耗就會很大;如果數值的範圍特別大,那麼對空間消耗的代價肯定也是不切實際的,所以這個演算法還有一定的侷限性。同樣,由於時間複雜度為 O(n+m),如果 m 比 n 大太多,則從時間上來說,效能也並不是很好。

但是實際上在使用桶排序的過程中,我們會使用類似散列表的方式去實現,這時的空間利用率會高很多,同時時間複雜度會有一定的提升,但是效率還不錯。

我們在開發過程中,除了對一些要求特別高並且資料分佈較為均勻的情況使用桶排序,還是很少使用桶排序的,所以即使桶排序很簡單、很快,我們也很少使用它。

桶排序更多地被用於一些特定的環境,比如資料範圍較為侷限或者有一些特定的要求,比如需要通過雜湊對映快速獲取某些值、需要統計每個數的數量。但是這一切都需要確認資料的範圍,如果範圍太大,就需要巧妙地解決這個問題或者使用其他演算法了。