從《基於比較的排序結構總結 》中我們知道:全依賴「比較」操作的排序演算法時間複雜度的乙個下界o(n*logn)。但確實存在更快的演算法。這些演算法並不是不用「比較」操作,也不是想辦法將比較操作的次數減少到 logn。而是利用對待排資料的某些限定性假設 ,來避免絕大多數的「比較」操作。桶排序就是這樣的原理。
桶排序的基本思想
假設有一組長度為n的待排關鍵字序列k[1....n]。首先將這個序列劃分成m個的子區間(桶) 。然後基於某種對映函式 ,將待排序列的關鍵字k對映到第i個桶中(即桶陣列b的下標 i) ,那麼該關鍵字k就作為b[i]中的元素(每個桶b[i]都是一組大小為n/m的序列)。接著對每個桶b[i]中的所有元素進行比較排序(可以使用快排)。然後依次列舉輸出b[0]....b[m]中的全部內容即是乙個有序序列。
[桶—關鍵字]對映函式
bindex=f(key) 其中,bindex 為桶陣列b的下標(即第bindex個桶), k為待排序列的關鍵字。桶排序之所以能夠高效,其關鍵在於這個對映函式,它必須做到:如果關鍵字k1很顯然,對映函式的確定與資料本身的特點有很大的關係,我們下面舉個例子:
假如待排序列k= 。這些資料全部在1—100之間。因此我們定製10個桶,然後確定對映函式f(k)=k/10。則第乙個關鍵字49將定位到第4個桶中(49/10=4)。依次將所有關鍵字全部堆入桶中,並在每個非空的桶中進行快速排序後得到如下圖所示:
對上圖只要順序輸出每個b[i]中的資料就可以得到有序序列了。
桶排序代價分析
桶排序利用函式的對映關係,減少了幾乎所有的比較工作。實際上,桶排序的f(k)值的計算,其作用就相當於快排中劃分,已經把大量資料分割成了基本有序的資料塊(桶)。然後只需要對桶中的少量資料做先進的比較排序即可。
對n個關鍵字進行桶排序的時間複雜度分為兩個部分:
(1) 迴圈計算每個關鍵字的桶對映函式,這個時間複雜度是o(n)。
(2) 利用先進的比較排序演算法對每個桶內的所有資料進行排序,其時間複雜度為 ∑ o(ni*logni) 。其中ni 為第i個桶的資料量。
很顯然,第(2)部分是桶排序效能好壞的決定因素。儘量減少桶內資料的數量是提高效率的唯一辦法(因為基於比較排序的最好平均時間複雜度只能達到o(n*logn)了)。因此,我們需要盡量做到下面兩點:
(1) 對映函式f(k)能夠將n個資料平均的分配到m個桶中,這樣每個桶就有[n/m]個資料量。
(2) 盡量的增大桶的數量。極限情況下每個桶只能得到乙個資料,這樣就完全避開了桶內資料的「比較」排序操作。當然,做到這一點很不容易,資料量巨大的情況下,f(k)函式會使得桶集合的數量巨大,空間浪費嚴重。這就是乙個時間代價和空間代價的權衡問題了。
對於n個待排資料,m個桶,平均每個桶[n/m]個資料的桶排序平均時間複雜度為:
o(n)+o(m*(n/m)*log(n/m))=o(n+n*(logn-logm))=o(n+n*logn-n*logm)
當n=m時,即極限情況下每個桶只有乙個資料時。桶排序的最好效率能夠達到o(n)。
總結:桶排序的平均時間複雜度為線性的o(n+c),其中c=n*(logn-logm)。如果相對於同樣的n,桶數量m越大,其效率越高,最好的時間複雜度達到o(n)。
當然桶排序的空間複雜度
為o(n+m),如果輸入資料非常龐大,而桶的數量也非常多,則空間代價無疑是昂貴的。此外,桶排序是穩定的。
桶排序在海量資料中的應用
一年的全國高考考生人數為500 萬,分數使用標準分,最低100 ,最高900 ,沒有小數,你把這500 萬元素的陣列排個序。
分析:對500w資料排序,如果基於比較的先進排序,平均比較次數為o(5000000*log5000000)≈1.112億。但是我們發現,這些資料都有特殊的條件: 100=
方法:建立801(900-100)個桶。將每個考生的分數丟進f(score)=score-100的桶中。這個過程從頭到尾遍歷一遍資料只需要500w次。然後根據桶號大小依次將桶中數值輸出,即可以得到乙個有序的序列。而且可以很容易的得到100分有***人,501分有***人。
實際上,桶排序對資料的條件有特殊要求,如果上面的分數不是從100-900,而是從0-2億,那麼分配2億個桶顯然是不可能的。所以桶排序有其侷限性,適合元素值集合並不大的情況。
源**
cpp**
#include
#include
typedef
struct nodekeynode;
void inc_sort(int keys,int size,int bucket_size)
for(int j=0;jkeynode *node=(keynode *)malloc(sizeof(keynode));
node->key=keys[j];
node->next=null;
//對映函式計算桶號
int index=keys[j]/10;
//初始化p成為桶中資料鏈表的頭指標
keynode *p=bucket_table[index];
//該桶中還沒有資料
if(p->key==0)else
} //列印結果
for(int b=0;bfor(keynode *k=bucket_table[b]->next; k!=null; k=k->next)
cout
cout
void main();
int size=sizeof(raw)/sizeof(int);
inc_sort(raw,size,10);
}
上面源**的桶內資料排序,我們使用了基於單鏈表的直接插入排序演算法。可以使用基於雙向鍊錶的快排演算法提高效率。
資料結構與演算法 桶排序
與基數排序法一樣,桶排序也是一種排序的思想。待排資料的值必須在乙個範圍內,然後把這個值域空間劃分為若干區塊,每乙個對應乙個桶,把待排陣列放入到合適的桶中,再對桶內的資料採用任意一種方式排序。整個時間複雜度分為兩部分,第一部分是入桶,為on,第二部分則是所有桶內排序的時間。演算法導論 中證明了這個整體...
資料結構與演算法 桶排序
桶排序可以看成是計數排序的公升級版,它將要排的資料分到多個有序的桶裡,每個桶裡的資料再單獨排序,再把每個桶的資料依次取出,即可完成排序。我們拿一組計數排序啃不掉的資料 500,6123,1700,10,9999 來舉例。第一步,我們建立 10 個桶,分別來裝 0 1000 1000 2000 200...
Python 資料結構與演算法 桶排序
簡單的桶排序 defbucksort a bucks dict 定義乙個桶變數,型別為字典 for i in a bucks.setdefault i,每個桶預設為空列表 往對應的桶中新增對應相同元素 print bucks a sort for i in range min a max a 1 i...