程序猿找工作必练内功:排序算法大总结(五)——线性时间排序算法(计数排序,基数排序,桶排序)

前几篇关于排序的博客中所讲的排序算法都是基于比较的,他们的时间复杂度的下界都是Ω(nlogn),这些算法对原始数据的分布没有要求。下面所说的几种O(n)时间复杂度的排序算法,它们都对原始数据的分布都有一定的假设,在假设成立的情况下,O(n)才成立。

首先说一下计数排序算法,该算法对输入的数据进行分类统计,然后再确定每个输入数据在输出时的位置,从而达到排序的目的。该算法得明确知道输入数据的取值范围,否则效率将很低,而且数据分布越均匀越好。设数据的个数为n,取值范围的区间长度为k,且满足k=O(n)时,该算法的时间复杂度才为O(n)。

基数排序算法是一种多关键字排序算法,每个数据具有多个关键字,各个关键字之间有确定的先后关系。基数排序从最低的关键字开始排序每个数据,依次到最高的关键字。比如一个三位数的数字数组,先按个位排序数组,再按十位,最后按百位,等所有关键字排完后,整个数组就排好序了。基数排序的时间复杂度是O(d(n+k)),其中d是每个关键字的位数(假设是整型值),n是关键字的个数,k是关键字每位的取值范围,当d和k确定时,基数排序的时间复杂度就是O(n)。

最后说桶排序,该算法假设数据要均匀分布在某个确定的范围内,然后将该范围划分为等概率的多个区间,即要求数据能够均匀的分布在每个区间内,这些区间就称为桶。根据自定的某种映射办法将数据映射到属于自己的桶中,然后使用其他排序算法排序每个桶中的数据,最后再依次输出桶中的数据即可完成所有数据的排序。桶排序的关键在于保证每个桶中的数据个数的平方和为O(n),n是数据的个数,因此,设计合适的桶就是排序的关键,也是该算法时间复杂度为O(n)的保证。

下面给出这三种算法的实现

#ifndef COUNTSORT_HPP_INCLUDED
#define COUNTSORT_HPP_INCLUDED
/**< 计数排序 */
template<class Type>
void countSort(Type *A, int n, int k)
{
    try
    {
    	int *c = new int[k+1];   //统计每种元素的个数
    	Type *B = new Type[n];   //保存输出结果
    	for(int i=0; i<=k; i++)  
			c[i]=0;
		for(int i=0; i<n; i++)    //统计
			c[A[i]]++;
		for(int i=1; i<=k; i++)    //确定<=i的元素的个数 ,即在输出数组中的位置
			c[i] += c[i-1];
		for(int i=n-1; i>=0; i--)   //输出
		{
			 B[c[A[i]]-1] =  A[i]; //注意个数和下标的关系
			 c[A[i]]--;
		}
		for(int i=0; i<n; i++)
			A[i] = B[i];
		delete []c;
		delete []B;
    }
    catch(bad_alloc e)
    {
    	return;
    }

}

#endif // COUNTSORT_HPP_INCLUDED

//基数排序
#ifndef RADIXSORT_HPP_INCLUDED
#define RADIXSORT_HPP_INCLUDED

#include "CountSort.hpp"

/** \brief  取整数n的从低到高的第m位
 *
 * \param n:整数
 * \param m:指定的位
 * \return int:第m位的数字
 *
 */

int getBit(int n,int m)
{
	while(m>1)
	{
		n /= 10;
		m--;
	}
	return n%10;
}
/** \brief 按照k进制整数序列A的第m位的大小排序A,使用计数排序法
 *
 * \param  A:整数序列
 * \param  n:序列长度
 * \param  k:进制
 * \param  m:第m位
 * \return
 *
 */

void countSort(int *A, int n, int k, int m)
{
	try
    {
    	int *c = new int[k];
    	int *B = new int[n];
    	char *bits = new char[n];
    	for(int i=0; i<k; i++)
			c[i]=0;
		for(int i=0; i<n; i++)
			bits[i] = getBit(A[i], m);
		for(int i=0; i<n; i++)
			c[bits[i]]++;
		for(int i=1; i<k; i++)
			c[i] += c[i-1];
		for(int i=n-1; i>=0; i--)
		{
			 B[c[bits[i]]-1] =  A[i];
			 c[bits[i]]--;
		}
		for(int i=0; i<n; i++)
			A[i] = B[i];
		delete []c;
		delete []B;
		delete []bits;
    }
    catch(bad_alloc)
    {
    	return;
    }
}
/** \brief 基数排序
 *
 * \param A:整数序列指针
 * \param d:整数的长度
 * \param n:整数的个数
 * \param k:进制(<=10)
 * \return
 *
 */

void radixSort(int *A, int d, int n, int k)
{
	for(int i=1; i<=d; i++)
		countSort(A, n, k, i); //从最低位开始排序,必须使用稳定的排序算法
}
#endif // RADIXSORT_HPP_INCLUDED

/**< 桶排序中,只要所有桶的大小的平方和与元素个数呈线性关系,
     即可在线性时间内完成排序 */

#ifndef BUCKETSORT_H_INCLUDED
#define BUCKETSORT_H_INCLUDED

#include<vector>
#include<algorithm>

using namespace std;

/** \brief 序列一般满足均匀分布在某个范围内即可实现线性排序 ,
实践中得根据不同问题设计合适的桶,此处拿浮点型来举例
*
* \param  A:浮点型序列指针
* \param  n:序列长度
* \param  start:序列元素的下界(>=1)
* \param  end:序列元素的上界(>=1)
* \return
*
*/
void bucketSort(float *A, int n, int start, int end)
{
    try
    {
        int bucketCount = end-start+1; //桶的个数
        vector<vector<float> > buckets;  //创建桶

        for(int i=0; i<bucketCount; i++)
            buckets.push_back(vector<float>());

        for(int i=0; i<n; i++)
            buckets[(int)(A[i]-start)].push_back(A[i]);//把元素加入到数据自己的桶中

        for(vector<vector<float> >::iterator it = buckets.begin();
        it!=buckets.end(); it++)
            sort(it->begin(), it->end());//排序每个桶中的元素

        int k=0;
        for(vector<vector<float> >::iterator it = buckets.begin();
        it!=buckets.end(); it++) //依次输出每个桶中的元素
        {
            for(vector<float>::iterator it2=it->begin();
             it2!=it->end(); it2++)
                A[k++] = *it2;
        }
    }
    catch(bad_alloc e)
    {
        return;
    }

}

#endif // BUCKETSORT_H_INCLUDED


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值