程序猿找工作必练内功:排序算法大总结(五)——线性时间排序算法(计数排序,基数排序,桶排序)
2013-12-31 23:03
417 查看
前几篇关于排序的博客中所讲的排序算法都是基于比较的,他们的时间复杂度的下界都是Ω(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)的保证。
下面给出这三种算法的实现
首先说一下计数排序算法,该算法对输入的数据进行分类统计,然后再确定每个输入数据在输出时的位置,从而达到排序的目的。该算法得明确知道输入数据的取值范围,否则效率将很低,而且数据分布越均匀越好。设数据的个数为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 ; //保存输出结果 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 ; char *bits = new char ; 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
相关文章推荐
- 2013年的总结
- OpenStack Cinder源码分析之四
- OpenStack Cinder源码分析之三
- Flex样式-TabNavigator篇
- 朋友们,技术需要积累
- Mysql设置远程
- ubuntu关于aptitude和apt-get
- 月夜斩
- OpenStack Cinder源码分析之二
- c#实现Javascript的encodeURIComponent()函数
- Gentoo firefox 安装 flash
- pthread编程基础
- UriMatcher类的简介
- 大数据存取的选择:行存储还是列存储?
- Leetcode: Permutations II
- OpenStack Cinder源码分析之一
- 英文操作系统下中文操作软件汉字显示为问号的问题解决方法
- 项目管理软件oKit V2.9于12月31日发布
- 2013年终总结
- 字里行间有学问——从一个方法的吐槽开始,说说几个编码实践