桶排序与基数排序

一、前言

    在各种内部排序算法中,有证明已经显示:只使用比较的一般排序算法在最坏情况下的时间复杂度为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;
	}
}





    





        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值