一、简介
基数排序(Radix Sort)属于“分配式排序”(Distribution Sort)。基数排序法是属于稳定性的排序。设待排序列为n个记录,d个关键码,关键码的取值范围为Radix,则进行基数排序的时间复杂度为O(d(n+Radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(Radix),共进行d趟分配和收集。
二、算法基本思想
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
LSD的算法思路如下:
将所有待比较数值(这里以正整数为例)统一为同样的数位长度,数位较短的数前面补零。设一整数的十进制位数表示为k1,k2...kd,其中k1为高位,kd为低位。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。即先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
MSD的算法思路如下:
MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个组中建立“子组”,将每个桶子中的数值按照下一数位的值分配到“子组”中。在进行完最低位数的分配后再合并回单一的数组中。即先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。所以,MSD方法用递归的思想实现最为直接。
三、算法实现(C++模板)
LSD的实现如下:
template <typename T>
int _RadixSort_GetNumInPos(T nData,const int pos)//获取特定位上的数
{
unsigned int* pData = (unsigned int*)&nData;
int nTemp = 1;
for(int i = 0;i < (pos - 1);i++)
{
nTemp *= 10;
}
return ((*pData) / nTemp) % 10;
}
template <typename T>
bool CountingSortWithRadixArray(T* pFirst,T* pLast,int* pRadixFirst,const int nMaxValue)//计数排序模板
{
bool bIsReverse = false; //判断输入数组指针是否逆序
T* pTemp = NULL;
if((pFirst == NULL) || (pLast == NULL)) //判断输入参数的正确性
{
cout<<"Input parameters error!"<<endl;
return false;
}
if(pFirst > pLast) //若输入数组指针逆序,则首尾指针交换
{
bIsReverse = true;
pTemp = pFirst;
pFirst = pLast;
pLast = pTemp;
}
const int nLength = pLast - pFirst + 1; //计算输入数据长度
int* pCountArray = new int[nMaxValue]; //对数据值进行计数,根据最大数据值进行数组申请
T* pTempArray = new T[nLength]; //辅助数组
pTemp = pRadixFirst;
for(int i=0;i<nMaxValue;i++)
{
pCountArray[i] = 0; //计数器清零
}
for(int i=0;i<nLength;i++) //对数据计数
{
pCountArray[*pTemp] = pCountArray[*pTemp] + 1;
++pTemp;
}
for(int i=1;i<nMaxValue;i++) //统计特定数据前的数据数目
{
pCountArray[i] = pCountArray[i] + pCountArray[i-1];
}
for(int i=nLength-1;i>=0;i--) //对数据进行重新排列
{
pTempArray[pCountArray[pRadixFirst[i]]-1] = pFirst[i];
--pCountArray[pRadixFirst[i]];
}
for(int i=0;i<nLength;i++) //将重新排列的数据重新赋值给原数组
{
pFirst[i] = pTempArray[i];
}
if(bIsReverse) //若输入数组倒序,则重新进行逆序
{
while(pFirst < pLast)
{
Swap(*pFirst,*pLast);
++pFirst;
--pLast;
}
}
delete []pCountArray;
delete []pTempArray;
return true;
}
template <typename T>
bool RadixSortLSD(T* pFirst,T* pLast) //基于计数排序的LSD实现模板
{
bool bIsReverse = false; //同上
T* pTemp = NULL;
if((pFirst == NULL) || (pLast == NULL)) //同上
{
cout<<"Input parameters error!"<<endl;
return false;
}
if(pFirst > pLast)
{
bIsReverse = true;
pTemp = pFirst;
pFirst = pLast;
pLast = pTemp;
}
int nLength = pLast - pFirst + 1; //计算数组长度
int* pRadixArray = new T[nLength]; //记录每位数据
T* pTempArray = new T[nLength]; //辅助数组
pTemp = pFirst;
bool bIsOK = false; //判断是否已经到达最高位
for(int i=0;i<nLength;i++)
{
pRadixArray[i] = 0;
}
int nPos = 1;
while(!bIsOK)
{
bIsOK = true;
for(int i=0;i<nLength;i++)
{
int nPosNum = _RadixSort_GetNumInPos(pTemp[i],nPos);//获取特定位数值
pRadixArray[i] = nPosNum;
if(pRadixArray[i] > 0)
bIsOK = false;
}
++nPos;
if(bIsOK) //若已对排序完最高位,则退出循环
break;
CountingSortWithRadixArray(pFirst,pLast,pRadixArray,RADIX_NUM);//这里采用稳定的计数排序按数组中每位数字进行排序
}
if(bIsReverse) //逆序数组
{
while(pFirst < pLast)
{
Swap(*pFirst,*pLast);
++pFirst;
--pLast;
}
}
delete []pRadixArray;
delete []pTempArray;
return true;
}
MSD的实现如下:
template <typename T>
bool RadixSortMSD(T* pFirst,T* pLast,const int nPos) //MSD实现模板
{
bool bIsReverse = false;
T* pTemp = NULL;
if((pFirst == NULL) || (pLast == NULL))
{
cout<<"Input parameters error!"<<endl;
return false;
}
if(pFirst > pLast)
{
bIsReverse = true;
pTemp = pFirst;
pFirst = pLast;
pLast = pTemp;
}
int nLength = pLast - pFirst + 1;
T* pBucket = new T[nLength];
int* pCount = new int[RADIX_NUM];
for(int i=0;i<RADIX_NUM;i++)
{
pCount[i] = 0;
}
for(pTemp=pFirst;pTemp<=pLast;pTemp++) //稳定计数排序
{
pCount[_RadixSort_GetNumInPos(*pTemp,nPos)]++;
}
for(int i=1;i<RADIX_NUM;i++)
{
pCount[i] = pCount[i] + pCount[i-1];
}
for(pTemp = pLast;pTemp>=pFirst;pTemp--)
{
int j = _RadixSort_GetNumInPos(*pTemp,nPos);
pBucket[pCount[j]-1] = *pTemp;
--pCount[j];
}
for(int i=0;i<nLength;i++) //将数组放入子数组中
{
pFirst[i] = pBucket[i];
}
delete []pBucket;
for(int i=0;i<RADIX_NUM;i++)
{
int nLeft = pCount[i];
int nRight = pCount[i+1] - 1;
if(nLeft < nRight && nPos > 1)
{
RadixSortMSD(pFirst+nLeft,pFirst+nRight,nPos-1); //对子数组递归进行MSD排序
}
}
delete []pCount;
if(bIsReverse)
{
while(pFirst < pLast)
{
Swap(*pFirst,*pLast);
++pFirst;
--pLast;
}
}
return true;
}
基数排序没有利用比较进行排序,而是对数据进行了另一个纬度的分解,分解后在利用复杂度为O(n)的桶排序或计数排序对分解后的数据进行排序,这样得到了更好的时间复杂度。但基数排序是否比基于比较的排序算法更好呢?然而,在实际应用中,基数排序时间复杂度O(n)中的常数因子会有所不同,对于要处理的n个关键字,尽管基数排序执行的遍数可能比快速排序要少,但每一遍所需的时间可能要长的多。哪个排序算法更好取决于底层机器的实现特性(例如,快速排序通常可以比基数排序更为有效地利用硬件缓存),同时还取决于特定输入的数据。此外,利用计数排序作为中间稳定排序的基数排序不是原地排序,而很多O(nlogn)的比较排序算法则可以做到原地排序。因此,当内存容量比较宝贵时,像快排这样的原地排序算法可能更为可取一些。
(做个宣传,哈哈,要想了解更多基本数据结构及算法实现详见我的GitHub,我会尽快更新!!)
GitHub地址是:https://github.com/daiyl0320/IntroductionToAlgorithms