有趣的桶排序

Jason(楊)發表於2017-07-21

  程式設計師的核心技能之一就是演算法,談到演算法,似乎都是從排序開始。對一組已知範圍的資料進行排序,最快的演算法是什麼呢?快速排序?希爾排序?非也,非也~是本文的主角“桶排序”!
  來看一個實際例子吧:已知一組範圍在0~10的資料(如:9,5,2,7,7),你有沒有什麼好方法編寫一段程式,將資料從大到小輸出呢?
  看到這樣的題目,相信很多人第一反應跟我一樣,就是將這些資料進行比較,然後進行位置的交換,最終實現排序。可桶排序徹底顛覆了這種想法——第一次看到桶排序時,確實被驚豔到了——還有這種操作?!原來排序不一定非得老老實實對原有資料進行位置調整!
  好了,回到我們的題目。我們只需要藉助一個一維陣列就可以解決問題。首先,我們申請一個長度為11的陣列int a[11],因為我們的資料範圍是0~10,而int a[11]的元素正好是a[0]~a[10]。開始的時候,我們將所有元素都初始化為0,表示這些數都未出現過。例如a[0]等於0,就表示待排序資料還未出現過0;同理,若a[9]等於1,則表示待排資料中9出現過1次。
  有了上面的說明,那接下來就非常簡單了,我們只需要遍歷待排陣列,然後將每個數值對應下標的元素加1(比如第一個數是9,我們就將a[9]加1)。你會發現,待排陣列遍歷完之後,a[0]~a[10]中的數值,其實就是0~10出現的次數,我們只需要按次數將下標列印出來就實現排序了。程式碼如下:

#include <stdio.h>
int main()
{
    int a[11],i,j,t;
    for(i=10; i>=0; i--)
    {
        a[i]=0;
    }

    for(i=1; i<=5; i++) //迴圈讀入5個待排數
    {
        scanf("%d",&t); //把每一個數讀到變數t中
        a[t]++; //進行計數
    }

    for(i=10; i>=0; i--)
    {
        for(j=1; j<=a[i];j++)
        {
            printf("%d ",i);
        }
    }

    printf("\r\n");    
    getchar();    
    return 0;

}

  執行該程式,我們可以隨意輸入5個0~10的數字,然後程式會將其按從大到小排序輸出,如下圖: 圖1
  接下來我們來看看時間複雜度。首先我們用了一個m次(m為桶的個數)的迴圈把桶清空;然後再用一個n次(n為待排序資料的個數)的迴圈,設定的資料的顯示次數;最後又進行了m+n次迴圈,把資料顯示出來。所以,整個排序演算法一共執行了m+n+m+n次。我們用大寫字母O來表示時間複雜度,因此該演算法的時間複雜度是O(m+n+m+n)即O(2*(m+n))。我們在說時間複雜度的時候可以忽略較小的常數,最終桶排序的時間複雜度為O(m+n)。還有一點,在表示時間複雜度的時候,n和m通常用大寫字母即O(M+N),這是一個非常快的排序演算法。
  桶排序雖快,但其實它是用空間在換時間。如果需要排序的數範圍非常寬,那我們就需要申請非常多的“桶”來儲存每一個數出現的次數。即使依然只是需要對5個數進行排序,這太浪費空間了!所以在選擇使用哪種排序演算法之前,還是需要根據實際情況,權衡好複雜度與空間的問題。
  

參考書籍:《啊哈!演算法》
說明:本文所提到的是簡化版的桶排序演算法,真正的同排序演算法要複雜些,有興趣的朋友可以自行搜尋學習。

相關文章