桶排序與基數排序

一、前言

    在各種內部排序算法中,有證明已經顯示:只使用比較的一般排序算法在最壞情況下的時間複雜度爲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;
	}
}





    





        

發佈了40 篇原創文章 · 獲贊 127 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章