排序算法三
基本的声明及公用函数库在【算法导论】排序算法 零中介绍。
gtest介绍及测试用例如下:测试框架之GTest
MIT《算法导论》下载:hereorhttp://download.youkuaiyun.com/detail/ceofit/4212385
源码下载:here orhttp://download.youkuaiyun.com/detail/ceofit/4218488
第一节讲了几个最简单的排序算法,时间复杂度都是n*n
第二节讲了几个稍微复杂的排序算法,时间复杂度可优化到nlogn,有些空间复杂度比较大。
本节进一步对时间进行优化,讲解几个时间复杂度最有情况下可达n ,但空间复杂度比较大的排序算法,典型的空间换取时间,而且这几个算法的使用范围有一定的限制。包括计数排序、基数排序、桶排序。
计数排序
计数排序大家应该都知道,时间复杂度n,空间复杂度与数值范围有关。
计数排序建立一个大数组,数组大小为可能出现的数值的最大值,然后将排序序列的数值为下标的位置置1,由于下标隐含有序,所以可得到有序序列。为兼容存在相同的数值,将大数组初始化为0,每次置位操作改为++即可。
例如
2 4 5 3 1
建立一个6个元素的数组A
遍历待排序序列,置位
A[2]=1,A[4]=1,A[5]=1,A[3]=1,A[1]=1;
然后将A从0-5遍历查找值为1的元素下标即可。
结果是
1 2 3 4 5
有相同数值的序列将置位改为++。
//计数排序,空间换取时间O(n)且不通用。要求取值为整数,且范围与内存有关,排序性能与范围有关。
void CountingSort(int *pArray,int cnt);
为了通用,先获取最大值,使用动态开辟内存。如果已知数值范围,可不获取最大值。
#include "SK_Common.h"
#include "SK_Sort.h"
void CountingSort(int *pArray,int cnt)
{
if(cnt <= 1)
return;
int i = 0,j = 0;
int index = 0;
//分配数组
int maxnum = get_max(pArray,cnt);
int *nums = new int[maxnum+1];
//初始化
for(i=0; i<maxnum; i++)
{
nums[i] = 0;
}
//置位
for(i=0; i<cnt; i++)
{
nums[pArray[i]]++;
}
index = 0;
//排序合并
for(i=0; i<maxnum; i++)
{
if(nums[i] == 0)
{
continue;
}
for(j=0; j<nums[i]; j++)
{
pArray[index++] = i;
}
}
delete[] nums;
}
基数排序
基数排序来源于原始的卡片计算机,比如10进制数,可从低位到高位依次排序,最终得到的是有序序列。每位排序时可使用计数排序或其他方法。其中排m位时要求除m位有序外,前m-1位的相对顺序不变。这种称为稳定的排序,例如冒泡法就是一种稳定的排序。
这个举个书上的例子:
7个三位数,先按照个位排序,再排十位,再排百位,最终得到有序序列。
//基数排序,从低位到高位依次排序,要求对一个位排序是稳定的,一层排序后除此层大小外相对位置不变。
//对一位的排序希望是高效的。时间复杂度、空间复杂度与单个位排序算法有关。
void RadixSort(int *pArray,int cnt);
本例只求说明,使用冒泡排序,所以效率比较低,可修改为其他稳定的排序方法。
#include "SK_Common.h"
#include "SK_Sort.h"
//按4位比较
static int compareby4bit(int val1,int val2,int pos)
{
int siftcnt = pos*4;
int dmask = 0xf;
int cmp1,cmp2;
cmp1 = (val1>>siftcnt) & dmask;
cmp2 = (val2>>siftcnt) & dmask;
return cmp1-cmp2;
}
//冒泡法是稳定的,只是效率有点低。
void _radix_sort(int *pArray,int cnt,int pos)
{
int i=0,j=0;
for(i=0;i<cnt-1;i++)
{
for(j=cnt-1;j>i;j--)
{
if(compareby4bit(pArray[j-1],pArray[j],pos) > 0)
{
exchange(&(pArray[j]),&(pArray[j-1]));
}
}
}
}
//按4位为一层
void RadixSort(int *pArray,int cnt)
{
int i = 0;
int maxbit;
//获取位数,按4位为单位排序
maxbit=get_bitcnt(get_max(pArray,cnt));
for(i=0; i<(maxbit+3)/4; i++)
{
_radix_sort(pArray,cnt,i);
}
}
桶排序
桶排序也是采用分而治之的原理,不过与分治法排序的区别在于
1、桶排序只分1层,分治法一层一层分到单个数值。
2、桶排序桶间先保证有序,然后各桶内再排序,最后合并。分治法是各部分先自己排序,部分直接不保证有序,最后合并。
例如
2 4 5 3 1
比如2个桶,可与3比较,<=3的一个桶,>3的一个桶
于是:
2 3 1
4 5
分别排序后为:
1 2 3
4 5
合并为:
1 2 3 4 5
//桶排序,先分桶,桶间有序,再桶内排序,最后合并。
//空间复杂度比较大,时间复杂度最优可为n
void BucketSort(int *pArray,int cnt);
本例分了10个桶,按照大小存储。
#include "SK_Common.h"
#include "SK_Sort.h"
//将pArray分到10个桶中,每个桶大小为cnt,依次排在tmpArray中,cntArray存储各个桶的大小。
static void _devid(int *pArray,int cnt,int *tmpArray,int*cntarray)
{
int i = 0;
int index;
int max = get_max(pArray,cnt);
int min = get_min(pArray,cnt);
int tunit = (max-min+1)/10;
if(tunit == 0)
tunit = 1;
//初始化个数
for(i=0;i<10;i++)
{
cntarray[i] = 0;
}
//分桶
for(i=0;i<cnt;i++)
{
index=(pArray[i]-min)/tunit;
if(index > 9)
index = 9;
tmpArray[index*cnt+cntarray[index]] = pArray[i];
cntarray[index]++;
}
}
//将10个桶合并为1个数组
static void _combin(int *pArray,int cnt,int *tmpArray,int *cntArray)
{
int index=0;
int i,j;
for(i=0;i<10;i++)
{
for(j=0;j<cntArray[i];j++)
{
pArray[index++] = tmpArray[i*cnt+j];
if(index >= cnt)
break;
}
if(index >= cnt)
break;
}
}
//假设分10个桶
void BucketSort(int *pArray,int cnt)
{
int i = 0;
int *tmpArray = new int[cnt*10];
int cntArray[10];
//分桶
_devid(pArray,cnt,tmpArray,cntArray);
//桶内排序
for(i=0; i<10; i++)
{
BubbleSort(tmpArray+i*cnt,cntArray[i]);
}
//合并
_combin(pArray,cnt,tmpArray,cntArray);
delete[] tmpArray;
}