算法思想:
//前提条件:
//待排序集合均为正整数
//已知其最大值(也可以排序的过程中求出)
//思想:假设我知道给定一个数,其前面有N个数比我小,那么,我就知道我应该位于数组中的第几个位置上
//实现方式:对于带排序集合中的每一个数,使用其值作为在另外一个数组中的下标,并将目标数组的值加1,这样,目标数组就记录了待排序集合中的每个元素所出现的次数
//该目标数组称为元素出现次数数组,长度为待排序数组中的最大元素的值+1
//然后我们再对出现次数数组,做一下转换:从第一个位置开始,让其值等于前面一个值与当前值的和,这样,这个处理过的出现次数数组就变成了,值等于5的数字,前面有多少个元素要小于等于5,也就实现了我们的一个核心思想:知道了前面有多少个数比我小,也就知道了我应该位于数组中的第几个位置上
从思想上来看,分为一下几步:
1、初始化一个大小为待排序集合最大值k+1的一个数组
空间代价 O(k)
2、遍历待排序集合,使用值作为下标,将k+1数组中的该下标位置上的值+1,也就是记录下这个数字一共出现了多少次
时间代价O(n)
3、整理下次数统计数组,转换为“该数字前面有多少个数字出现”
时间代价O(K)
4、初始化一个结果集数组
空间代价O(n)
5、从后往前遍历待排序数组,通过其在次数统计数组中的值,得出前面有多少个数字,也就是该数字当前应该放在第几个位置,将其放入到新的结果集数组中
时间代价O(n)
第五部中从后往前遍历待排序数组,保证了排序算法的稳定性
结论:一个最大值为k的n个元素的待排序数组,其执行计数排序的时间代价为:
O(2n+k),空间代价为O(n+k)
显然,二者都是一个线性的代价,比前面说的所有的比较排序的最优代价nlgn均表现良好
算法实现:
class CountingSort {
public:
//求最大值
int caculateMaxValue(int * data,int size){
int maxValue = *data;
for (int i =1; i < size ; i++) {
if(*(data + i) > maxValue){
maxValue = *(data + i);
}
}
return maxValue;
}
//初始化一个元素为0、长度为size的数组
int * initZeroData(int size){
int * data = new int [size];
memset(data, 0, sizeof(int) * size);
return data;
}
//构建 出现次数 数组
int * buildCountingArray(int * data,int maxValue,int size){
int * countingArr = initZeroData(maxValue + 1);
for(int i=0;i<size;i++){
(*(countingArr + *(data + i)))++;
}
return countingArr;
}
//将 出现次数 数组做下处理,转变成给定的数组,前面有多少个比它小的数字
void dealCountingArray(int * countingArr,int maxValue){
for (int i =1; i <= maxValue; i++) {
*(countingArr + i) += *(countingArr + i -1);
}
}
//执行计数排序
int * doCountingSort(int * data,int size){
//用来存放结果
int * resultArr = initZeroData(size);
//求最大值
int maxValue = caculateMaxValue(data,size);
//每个数字出现的次数统计数组
int * coutingArr = buildCountingArray(data,maxValue,size);
//处理下次数统计数组,变成某某元素前面有多少个元素比其小
dealCountingArray(coutingArr,maxValue);
//为了保证稳定性,我们从后往前遍历待排序集合,找出对应的其前面有多少个数比其小的个数,然后定位输出到正确的结果数组中去
for(int i =size -1;i>=0;i--){
int beforeLowerNumberCount = *(coutingArr + *(data + i));
*(resultArr + beforeLowerNumberCount - 1 ) = *(data + i);
//已经拿走一个了
*(coutingArr + *(data + i)) = beforeLowerNumberCount - 1;
}
//清理资源
delete [] data;
delete [] coutingArr;
return resultArr;
}
//排序入口
int * do_sorting(int * data,int size){
return doCountingSort(data, size);
}
};
算法总结:
时间代价是线性的,而且是稳定性的排序算法,但是有一定的空间代价和局限性,其虽然突破了比较算法的nlgn的最优代价的上限,但是其有一定的前提必要条件,并且在空间上也有一定的牺牲,使用时要注意其平衡点