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