现在的位置: 首页 > 综合 > 正文

基数排序简介及LSD、MSD实现

2013年12月01日 ⁄ 综合 ⁄ 共 4351字 ⁄ 字号 评论关闭

一、简介

    基数排序(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

抱歉!评论已关闭.