基数排序
基数排序是一种非比较型的排序算法。
思想:
将整数分割成不同的数字,再对每一位进行比较,先从各位开始,一次往上走,直到某一位都相等。它是在一种老式穿卡机上的算法。当然在典型的顺序随机存取计算机上,又是采用基数排序算法对多个关键字域进行排序。例如我们对生日的排序,不仅可以从年份开始,也可以从日期开始。
来源:
在老式的穿卡机中,利用卡片记录数据,每一个卡片中有多个列,每一列有12个位置,可以在任一个位置进行打卡,然后将卡片进行排序。但是我们一次只能看到卡片中的一列,当然基于原来的思想是从高位开始排序,与桶排序一样,将数据进行分割,但好似我们知道在桶排序中我们有一个存储索引的数组。在这里我们还要再添加另外的卡片进行数据的记录,显然是不合理的。这个时候只能从低位开始往高位进行排序,当高位的数字一样,依旧保持着上次的排序结果。
模拟:
假如有几个数字:329,457,657,839,436,720,355。那么排序结果为:每一列为一次运行结果
329 | 720 | 720 | 329 |
457 | 355 | 329 | 355 |
657 | 436 | 436 | 436 |
839 | 457 | 839 | 457 |
436 | 657 | 355 | 657 |
720 | 329 | 457 | 720 |
355 | 839 | 657 | 839 |
效能分析:
若给定n个d位数,每一个数位可以去k中可能的值。如果所用的稳定排序需要(n+k)的时间,那么技术排序算法能够以d(n+k)的时间正确的对这些数进行排序。为什么会有(n+k)呢,这个让我们记起比较排序,貌似都是n(lgn)的情况,是的,计数排序,因为每一位数我们都是有范围的,若是10进制的话,不过最大的是0-9的范围。对辅助数组赋初值为k,对辅助数组计算元素出现次数为n。
与快速排序的分析:
基数排序看上去似乎要比快速排序的平均情况好一些。计数排序作为中间稳定排序的基数排序不是在位的排序,而快排就可以做到。快排比基数排序更为有效利用硬件缓存。所以当内存比较珍贵的时候还是用快排。并且,在每一遍处理的时间要比快排长。另外它的隐含因子就是d(n+k)前面的数值并不确定。我们依旧不能够知道哪个效率会高一些。
代码:
struct RadixTempNode
{
public:
int bitNum;//元素出现的次数
int bitLocation;//最后这个元素所在的位置
RadixTempNode()
{
bitNum=0;
bitLocation=0;
}
};
template<class T>
T* RadixList<T>::RadixSort(T *radixArray,int length)
{
//思想:将所有的待比较整数统一为有相同位数长度,数位较短的前面补零。
//然后从地位到高位依次进行一次排序。在每个位上可以采用效率较好的排序算法
//这里我们采用的是计数排序,但是计数排序不是稳定的排序算法,于是我们要进行改进
RadixTempNode tempArray[10];//每一位都是0-9,这里假设是10进制的情况
T *resultArray=new T [length];//排序后的数组
bool bitPos=true;
int modeData=10;
int bitData=0;
while(bitPos)//当位数存在时,下面将利用到的是计数排序
{
bitPos=false;
for(int i=0;i<10;i++)
{
tempArray[i].bitNum=0;//赋初值为0
tempArray[i].bitLocation=0;
}
for (int i=0;i<length;i++)
{
*(resultArray+i)=0;//清空结果数组
}
for(int i=0;i<length;i++)
{
bitData=*(radixArray+i)%modeData/(modeData/10);//得到位数上的值
if (bitData!=0)
{
bitPos=true;
}
tempArray[bitData].bitNum+=1; //tempArray中索引就是余数,也就是要排序的数值,
//而tempArray中的值为这个排序的数出现的次数
}
if (!bitPos)
{
break;
}
tempArray[0].bitLocation=tempArray[0].bitNum;//加一个存储空间保证稳定算法
for (int j=1;j<10;j++)
{
tempArray[j].bitLocation=tempArray[j].bitNum+tempArray[j-1].bitLocation;//所在位置
}
for (int k=0;k<length;k++)
{
bitData=*(radixArray+k)%modeData/(modeData/10);//计数排序并不是稳定的排序算法,因此后面要改进
//找到在temArray中索引为RadixArray[index]的在的数值,这个数值就是在resultArray中的位置
*(resultArray+tempArray[bitData].bitLocation-tempArray[bitData].bitNum)=*(radixArray+k);
tempArray[bitData].bitNum-=1;//这里必须减一否则会一直覆盖原来位置上的值,而其它的地方值却一直为0
}
cout<<"结果为:"<<endl;
for (int i=0;i<length;i++)
{
cout<<*(resultArray+i)<<" ";
}
cout<<endl;
modeData*=10;
//radixArray=resultArray;//若仅仅是这一条语句,没有后面的,这个时候是指针赋值,而resultArray中数据在后面中都会为0
for (int index=0;index<length;index++)
{
*(radixArray+index)=*(resultArray+index);
}
}
return radixArray;
}
void RadixSortTest()
{
int testArray[]={329,457,657,839,436,720,355};
cout<<"初始数组为:"<<endl;
for(int i=0;i<7;i++)
{
cout<<*(testArray+i)<<" ";
}
cout<<endl;
RadixList<int>*radixSort=new RadixList<int>();
int *resultArray=radixSort->RadixSort(testArray,7);
cout<<"基数排序后的结果为:"<<endl;
for (int i=0;i<7;i++)
{
cout<<*(resultArray+i)<<" ";
}
cout<<endl;
}
运行结果如上所示
小结:
1、作为非比较的排序算法,基数排序有点类似桶排序,不过一个是从高位,一个是从地位进行。
2、记得在对tempArray中赋值时与原来的计数不同,之前的计数是tempArray[indec]+=tempArray[i-1].在这里因为我们使用了一个结构体保证稳定性,所以是忽视了0位置上的值。所以tempArray[0].bitLocation=tempArray[0].bitNum;。
3、其核心代码为:*(resultArray+tempArray[bitData].bitLocation-tempArray[bitData].bitNum)=*(radixArray+ktempArray[bitData].bitNum-=1;