一、前言
在各種內部排序算法中,有證明已經顯示:只使用比較的一般排序算法在最壞情況下的時間複雜度爲O(NlogN),但是在已知某些額外信息或特殊情況下,可以達到線性時間的排序時間複雜度,就是桶排序和基數排序。
二、桶排序
任何算法首先始於思想(邏輯),只有思想(邏輯)是對的,才需要考慮優化算法的時間消耗和內存消耗。所謂“桶排序”,即把待排序數列裝進一個形如桶的數據結構中,這裏的桶中只保存桶中有多少個數據,並不保存實際的數值(其實是保存了的,只是比較巧妙)。上面已經說過,桶排序需要知道一些額外的關於待排序數列的信息,這個信息就是待排序數組的最大值,因爲這決定了桶結構的大小。
桶排序思想:
(1)構建桶。待排序數列最大值爲M,以數組Bucket[M]作爲桶結構,初始化桶結構所有元素爲0,即初始時每個桶中保存的數據爲0個;
(2)掃描待排序數組。依次掃描待排序數組a[N],依次Hash(散列)待排序數組中每個元素到桶結構中,這裏的Hash函數是Hash(x) = a[i],一旦某個元素Hash到某一個桶中,則該桶保存的數據個數+1;(仔細體會這個過程,這裏Hash的作用時把a[i]與桶結構數組的下標關聯)
(3)掃描桶結構數組。依次掃描桶結構,如果桶結構中某一元素不等於0,則表示該桶中至少保存有一個待排序數組中的數值,那這個待排序數組中的數值是多少呢?根據第(2)步中的Hash過程,這個數值就是桶結構對應的下標值,即只要桶結構該元素不等於0,就依次打印桶結構對應位置的下標,所得結果就是排序後結果。
如待排序數組時:4,1,2,56,1,20,3,48,50,48 這裏共10個數,最大值M=56,根據桶排序思想,桶排序流程如下:
(1)構建桶
(2)掃描待排序數組,進行Hash操作
(3)掃描桶結構數組,依次打印下標結果爲:1,1,2,3,4,20,48,48,50,56。 排序完成,具體代碼如下:
void BucketSort(int *a, const int N, const int M)
{
//構建桶
int *bucket = new int[M];
for(int i=0;i<M;i++)
bucket[i] = 0;
//依次掃描待排序數組,Hash操作
for(int i=0;i<N;i++)
bucket[ a[i] ] += 1;
//依次掃描桶結構,打印下標
for(int i=0;i<M;i++)
{
while(bucket[i] != 0)
{
cout << i << " ";
--bucket[i];
}
}
}
三、基數排序
瞭解了桶排序,有沒有什麼想法?首先它的時間複雜度是多少?O(M+N),主要體現在掃描桶結構的過程中,那麼它的空間複雜度又是多少呢?是O(M)。回過頭來想一想,你排序10個元素,用了56個內存空間,那要是還是剛剛那個數列,只是最大值變了,變爲999,那麼你的空間複雜度變爲1000,也就是說對10個數進行排序,用了1000個額外空間,這未免也太奢侈了吧,爲了解決這個問題,就有了基數排序(其實基數排序的原理在很久以前就有了)。
怎麼解決呢?那就是用多趟桶排序,每趟只排序所有數列中數值中的某一位,如個位、十位、百位等,同理,基數排序也需要知道待排序數列的額外信息,但不是最大值本身,只是最大值的位數P,P就是桶排序的趟數。
如何進行多趟排序呢?如果從最低位(個位)開始,沒進行一趟桶排序,完成之後需要先收集這一趟的排序結果,作爲下一趟排序的輸入,所以整個基數排序是一個分配和收集循環的過程。這並沒有比桶排序多出多少工作量,只有兩個方面:(1)基數排序需要多趟桶排序;(2)每一趟桶排序之後需要收集當前的排序結果,作爲下一趟排序的輸入。對於第一點,可以用用for循環跟蹤P實現多趟排序控制,對於第二點,難點在於怎麼收集當前排序結果?用什麼結構來收集?答案是用二維數組收集,爲什麼是二維數組,看看下面的例子:
如果待排序數組爲:64, 8, 216, 512, 27, 729, 0, 1, 342, 125,最大數有3位,則需要3趟桶排序,則第一趟(按個位數值排序)排序結果爲:
收集第一趟排序結果,收集原則爲從下到上,依次收集每個桶中的元素;
第二趟排序(按十位數值排序)結果爲:
根據收集原則,第一次桶排序結果爲:0,1,8,512,216,125,27,729,343,64,以該數列爲基礎,進行第三趟桶排序(按百位數值排序),結果如下:
再收集桶排序結果爲:0,1,8,27,64,125,216,343,512,729.由於這是最後一趟桶排序,則所得結果即爲最後排序結果。
基數排序實現如下:
//計算待排序數列最大數的位數
int MaxLength(int *a, const int N)
{
int d = 1;
int p = 10;
for(int i=0; i<N; i++)
{
while(a[i] >= p)
{
p = p*10;
++d;
}
}
return d;
}
void Sort(int *a,const int N)
{
int d = MaxLength(a,N); //待排序數列最大數的位數
int k = 1;
vector< vector<int> > A(10); //桶結構,用來存儲每次桶排序過程的值
vector<int> temp; //中間內存,用來收集每次桶排序結果
for(int p=0;p<d;p++)
{
for(int i=0;i<10;i++)
{
A.at(i).clear(); //每次桶排序開始清零桶結構
}
for(int i=0;i<N;i++)
{
int s = a[i] / k;
int base = s % 10;
A.at(base).push_back(a[i]); //填充桶結構
}
for(int j=0; j<10; j++)
{
vector<int>::iterator itr;
for(itr=A[j].begin(); itr!=A[j].end();itr++)
{
temp.push_back(*itr); //收集桶排序結果
}
}
for(int i=0;i<N;i++)
{
a[i] = temp[i]; //將最新的桶排序結果賦值給原來待排序數組,進行下一次桶排序
}
temp.clear();
k = k * 10;
}
}