一、算法原理
基数排序算法又称桶排序,是一种原理简单实现相对麻烦一点的算法。基数排序属于稳定排序法,适用于数值比较大的数据之间的排序。
常见的内部排序算法中都使用到了元素之间的比较大小,而基数排序算法不涉及元素之间的比较,而是根据基数将数据存放到相应的“桶”里,经过多趟这样的存放过程,也就是一个渐进排序过程,就可以实现对一组散乱数据进行排序。总趟数是数组中位数最多的那个元素的位数。其实读到这里,大家是不是有一种很熟悉的感觉,没错,这跟创建Hash(散列)表的原理类似。
第一趟排序的基数是1,即从“个位”上的数字开始,将根据每一个元素的“个位”数字将该元素存到到与数字相同的“桶里”,得到一次排序结果。第二趟排序的基数是10,在第一趟排序的基础上,根据“百位”上的数字将元素重新放入相应的“桶”里,得到新一轮的排序。依次类推,直到所有元素中的最高位已经处理完毕,结束整个排序过程。从这里就可以看出基数排序就是一个渐进的排序过程。
需要注意的是使用静态数组进行基数排序时,并不是真的把元素放入“桶”里,而是把某个“位”上数字相同的元素个数存放入“桶”里。这样做是为了方便排序。“桶”的个数是10,这是因为阿拉伯数字就是10个,分别是0,1,2,3,4,5,6,7,8,9。每个桶中的值就是相同“位”上数字相同的元素个数。
下面以具体的例子演示一下基数排序的基本原理。
Demo:设有数据如下:
准备工作:
1)创建10个桶。
2)求出最大的元素12608(也就是这里涉及到了比较大小,此外再无,童叟无欺),计算出其总位数是5,用于控制排序的总趟数。其实每一趟就是针对一个“位”上的元素进行排序。
3)为了方便求每一个元素不同“位”上的元素,设定一个基数,其取值分别是1,10,100,…,即第一趟排序时,值为1,用于求“个位”上的数字;第二趟排序时值为10,用于求“十位”上的数字,第三趟排序时,值为100,用于求“百位”上的数字,等等。
第一趟排序:
基数是1,根据“个位”数字将元素放入对应的桶里。
之后将根据“桶”号大小,将“桶”里元素按照桶号的大小顺序放入数组,得到如下结果:
第二趟排序:
基数是10,在第一趟排序的基础上,根据“十位”数字将元素放入对应的桶里。
之后再将“桶”里元素按照桶号的大小顺序放入数组,结果如下:
第三趟排序:
基数是100,在第二趟排序的基础上,根据“百位”数字将元素放入对应的桶里。
之后再将“桶”里元素按照桶号的大小顺序放入数组,结果如下:
第四趟排序:
基数是1000,在第三趟排序的基础上,根据“千位”数字将元素放入对应的桶里。
之后再将“桶”里元素按照桶号的大小顺序放入数组,结果如下:
第五趟排序:
基数是10000,在第四趟排序的基础上,根据“万位”数字将元素放入对应的桶里。
之后再将“桶”里元素按照桶号的大小顺序放入数组,结果如下:
至此,基数排序结束。
在上述演示过程中,可以发现,在每一趟的排序中,每个“桶里”存放的元素个数都可能不同,如果直接把元素存放到桶里,就给算法的具体实现带来了很大麻烦。为了解决这个麻烦,“桶”里不再存储元素,而是改为存储元素的个数,同时引入一个新的数组作为缓存,存储每一趟的排序结果,之后再把排序结果存储到原数组中,这样就可以方便的实现桶排序了。
例如第一趟排序中的“桶”里数据就行可以修改为存放元素的个数,具体如下:
再做元素值的累加得到:
这样就可以得到原数组中每一个元素在排序后数组中的下标。有童鞋会突然发现有的桶里存放了多个元素,如何解决其位置呢?这个其实很简单,“桶”的元素值每用一次,就执行减1操作,就可以解决了。例如用bucket表示桶数组,bucket[4]中实际存放了两个元素,对应的原数组的元素分别是14和8844。bucket[4]的值是5,表示其中存放的元素在新数组的下标值是5,即当取走元素8844时,8844在新数组的下标是5,然后bucket[4]执行减1操作,其值就是4了,再取走元素14时,元素14在新数组的下标就是4了。
二、基数排序算法
记待排序的数组为data,元素个数为length。
**Step1:**创建桶bucket,有10个元素,初始值均为0,用于存放某“位”数字与桶元素编号相同的数组元素的个数;
求出数组的最大元素,同时算出其总位数count;
定义基数变量,例如base,其初始值为1;
分配缓存buff,其元素个数与原数组元素个数相同。
**Step 2:**数组的每个元素data[i]除以base之后对10求余数,记为k,执行bucket[k]++;转下一步;
**Step 3:**重新计算“桶”bucket中的每一个元素的值,让其等于前面元素之和,即
bucket[k+1] += bucket[k]
这样就可以计算出原数组 data中的每一个元素在新数组buff中的位置。(这一步是不是很神奇);转下一步;
**Step 4:**根据“桶”里的元素,也就是位置序号,将原数组data中的元素依次存放到buff中,再将buff中元素依次存入data中;转下一步;
**Step 5:**base *= 10,count–,判断count > 0, 如果为真,则转step2,否则结束排序。
三、基数排序的C程序
1. 基数排序代码
//利用桶排序算法对数组data进行从小到大排序
void RadixSort( int data[], int length )
{
int i, k, maxVal, count, base;
int bucket[10] = { 0 };
int *buff = new int[ length ];
//求数组中的最大元素
maxVal = data[0];
for( i = 1; i < length; i++ )
{
if( data[i] > maxVal )
{
maxVal = data[i];
}
}
//printf( "maxVal = %d\n", maxVal );
//计算最大元素的位数
count = 0;
while( maxVal > 0 )
{
count++;
maxVal /= 10;
}
//printf( "count = %d\n", count );
base = 1;
for( k = 1; k <= count; k++ )
{
//求每一个元素的 base位上的数字,对应的桶元素做累加
for( i = 0; i < length; i++ )
{
bucket[ ( data[i] / base ) % 10 ]++;
}
//重新计算桶元素的值,每一个元素都是其前面元素之和
//如此操作,可以得到每一个桶中的元素在排序之后的位置下标
for( i = 1; i < 10; i++ )
{
bucket[i] += bucket[i-1];
}
//根据桶元素的值,将data中的元素重新排位存入buff中
for( i = length-1; i >= 0; i-- )
{
buff[ bucket[ ( data[i] / base ) % 10 ] - 1 ] = data[i];
bucket[ ( data[i] / base ) % 10 ]--;
}
//将buff中元素对位存放到data中
for( i = 0; i < length; i++ )
{
data[i] = buff[i];
}
//清空桶,即让桶里元素归0
for( i = 0; i < 10; i++ )
{
bucket[i] = 0;
}
//base的值乘以10,之后进入下一趟排序
base *= 10;
}
delete[] buff;
}
2.完成测试代码
#include"stdio.h"
void RadixSort( int data[], int length );
int main()
{
int data[] = { 255, 12608, 31, 14, 21, 121, 268, 8844 };
int length = 8;
printf( " Initial Array : " );
for( int i = 0; i < length; i++ )
{
printf( "%8d", data[i] );
}
printf( "\n" );
RadixSort( data, length );
return 0;
}
//利用桶排序算法对数组data进行从小到大排序
void RadixSort( int data[], int length )
{
int i, k, maxVal, count, base;
int bucket[10] = { 0 };
int *buff = new int[ length ];
//求数组中的最大元素
maxVal = data[0];
for( i = 1; i < length; i++ )
{
if( data[i] > maxVal )
{
maxVal = data[i];
}
}
//printf( "maxVal = %d\n", maxVal );
//计算最大元素的位数
count = 0;
while( maxVal > 0 )
{
count++;
maxVal /= 10;
}
//printf( "count = %d\n", count );
base = 1;
for( k = 1; k <= count; k++ )
{
//求每一个元素的 base位上的数字,对应的桶元素做累加
for( i = 0; i < length; i++ )
{
bucket[ ( data[i] / base ) % 10 ]++;
}
//重新计算桶元素的值,每一个元素都是其前面元素之和
//如此操作,可以得到每一个桶中的元素在排序之后的位置下标
for( i = 1; i < 10; i++ )
{
bucket[i] += bucket[i-1];
}
//根据桶元素的值,将data中的元素重新排位存入buff中
for( i = length-1; i >= 0; i-- )
{
buff[ bucket[ ( data[i] / base ) % 10 ] - 1 ] = data[i];
bucket[ ( data[i] / base ) % 10 ]--;
}
//将buff中元素对位存放到data中
for( i = 0; i < length; i++ )
{
data[i] = buff[i];
}
//清空桶,即让桶里元素归0
for( i = 0; i < 10; i++ )
{
bucket[i] = 0;
}
//base的值乘以10,之后进入下一趟排序
base *= 10;
//
//向屏幕输出每一趟排序结果,便于观察
printf( " No. %d time sort : ", k );
for( i = 0; i < length; i++ )
{
printf( "%8d", data[i] );
}
printf( "\n" );
//
}
delete[] buff;
}
3.测试运行结果: