这篇文档我们来讲两种非比较排序,计数排序,基数排序
计数排序
计数排序是一种非比较算法,其时间复杂度为O(N+K)
举例说明
先用一个例子来说明计数排序算法,比如需要排序的集合为{1, 2, 1, 0, 4, 5},在该集合中,最大的数值为5,那么通过遍历整个集合,
可以得到这样的数组
int counter[] = {1, 2, 1, 0, 1, 1}
0, 1, 2, 3, 4, 5
counter数组描述了被排序数组里有1个0, 2个1, 1个2, 0个3, 1个4和1个5,当这个数组形成时,排序也就结束了。
代码设计
1)根据集合中最大的数值K,来申请辅助空间counter数组
2)遍历被排序集合,讲计数记录到counter数组中
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int data[] = {1, 2, 1, 0, 4, 5};
// 计数排序参数列表
// int d[] 为待排序数据列表
// int n 为待排序数据个数
// int min, max为待排序数据中最大和最小的数,通过其计算待排数据跨度K
void sort_counter(int d[], int n, int k)
{
int i, j = 0;
k++; // 实际申请空间比K大1
// 申请辅助空间
int* counter = malloc(sizeof(int)*k);
memset(counter, 0, sizeof(int)*k);
// 计数
for(i=0; i<n; ++i)
{
++counter[d[i]];
}
// 讲计数结果保存到待排数据空间
for(i=0; i<k; ++i)
{
while(counter[i]-- > 0)
{
d[j++] = i;
}
}
// 释放辅助空间
free(counter);
}
int main(void)
{
sort_counter(data, 6, 5);
int i;
for(i=0; i<6; ++i)
{
printf("%d\n", data[i]);
}
return 0;
}
特点
计数排序的特单是时间复杂度,与其他的排序算法不同,它的时间复杂度为O(N+K),这个时间复杂度表明了当K相对比较大时,不适合使用,比如对集合{1, 0, 100}
但是对于N远大于K的情况下,是相当适合的
基数排序
在数量大时,计数排序需要大量的辅助空间,并且时间复杂度有可能比较大,所以推出基数排序。
举例说明
假如有待排序数据 data[] = {312, 213, 545, 893};
先排序个位数:排序结果为 312, 213, 893, 545
再排序十位数:排序结果为 321, 213, 545, 893
再排序百位数:排序结果为 213, 312, 545, 893
完毕
对位数的排序,可以使用计数排序,速度快而且稳定。
代码设计
1)获取待排序数据中的最大位数
2)对位数进行循环,按位对待排序数据进行计数排序,并将其中间结果保存到临时空间
3)将临时数据保存到待排序数据,继续步骤2)
代码实现
#include <stdio.h>
// int data[] = {1, 100, 321, 121, 333, 586, 1100};
// 该函数计算data中最大位数,本例子中,最大位数是1100,所以结果是4
int maxbit(int data[],int n)
{
int d = 1; //保存最大的位数
int p =10;
for(int i = 0;i < n; ++i)
{
while(data[i] >= p)
{
p *= 10;
++d;
}
}
return d;
}
// 基数排序
void sort_radix(int data[],int n)
{
int d = maxbit(data, n); // 计算位数
int * tmp = new int[n]; // 中间变量,用来存储中间排序结果
int * count = new int[10]; // 计数排序中的计数器
int i,j,k;
int radix = 1;
// 根据最大位数进行循环,对没一位进行计数排序
for(i = 1; i<= d;i++)
{
// 初始化计数器
for(j = 0; j < 10; j++)
count[j] = 0;
// 对位数进行计数排序
for(j = 0; j < n; j++)
{
k = (data[j]/radix)%10; // 注意这里进行取模
count[k]++; // 计数
}
for(j = 1; j < 10; j++)
count[j] = count[j-1] + count[j];
// 将排序中间结果保存到tmp
for(j = n-1; j >= 0;j--)
{
k = (data[j]/radix)%10;
tmp[count[k]-1] = data[j];
count[k]--;
}
// 将中间结果保存到data
for(j = 0;j < n;j++)
data[j] = tmp[j];
// 取模时的被除数,需要提高一位
radix = radix*10;
}
delete [] tmp;
delete [] count;
}
int main()
{
int data[] = {1, 100, 321, 121, 333, 586, 1100};
sort_radix(data, 7);
for(int i=0; i<7; i++)
{
printf("%d\n", data[i]);
}
return 0;
}
特点
基数排序比较适合对取值很大的数进行排序,也可用来对字符串进行排序。
但基数排序的缺点是不呈现时空局部性,因为在按位对每个数进行排序的过程中,一个数的位置可能发生巨大的变化,所以不能充分利用现代机器缓存提供的优势。同时计数排序作为中间稳定排序的话,不具有原地排序的特点,当内存容量比较宝贵的时候,还是有待商榷。