您的位置:首页 > 其它

非比较排序-----计数排序,基数排序。

2016-11-26 21:35 211 查看
排序总归来说可分为两大类,比较排序与非比较排序。比较排序就是我们常用到的冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序,归并排序。非比较排序不常用,但是在对一些特殊的情况进行处理时,它的速度反而更快。

1、计数排序

排序原理:利用哈希的方法,将每个数据出现的次数都统计下来。哈希表是顺序的,所以我们统计完后直接遍历哈希表,将数据再重写回原数据空间就可以完成排序。

注意事项

①因为要建立哈希表,计数排序只适用于对数据范围比较集中的数据集合进行排序。范围分散情况太浪费空间。如我有100个数,其中前99个都小于100,最后一个数位是10000,我们总不能开辟10000个数据大小的哈希表来进行排序吧!这样空间就太浪费了。 此时,就得考虑其他的算法了。当然,这个例子也可能不恰当,这里只是想让你们直观的理解。

②除了上面那种情况,还有一个问题,例如我有一千个数,1001~2000,此时我的哈希表该怎么开辟呢? 开0~2000个?那前面1000个空间就浪费了!直接从1001开始开辟?你想多了!所以这种情况我们就需要遍历一遍数据,找出最大值与最小值,求出数据范围。范围 = 最大值 - 最小值+1。 例如,2000-1001+1 = 1000,这样我们就开辟0~1000个空间,用1代表1001,1000代表2000。节省了大量的空间。

③肯定有同学想到用位图(BitMap)来做,但是位图(BitMap)有局限性,它要求每个数据只能出现一次。算法有些复杂,但是可以尝试。

时间复杂度分析:综上所述,我们总需要两遍遍历,第一遍统计字数据出现次数,遍历原数据,复杂度为O(N),第二遍遍历哈希表,向原空间写数据,遍历了范围次(range),时间复杂度为O(range),所以总的时间复杂度为O(N+range)。

空间复杂度分析:开辟了范围(range)大小的辅助哈希表,所以空间复杂度为O(range)。

下面我对一组数据进行排序做出图解:



c++代码实现:

void CountSort(int *a, int n)
{
assert(a);

int max = a[0];
int min = a[0];
//选出最大数与最小数,确定哈希表的大小
for (int i = 0; i < n; ++i)
{
if (a[i] > max)
{
max = a[i];
}
if (a[i] < min)
{
min = a[i];
}
}

int range = max - min + 1;
int *count = new int[range];
memset(count, 0, sizeof(int)*range);//当初始化成0或-1的时候可以用memset,其他情况均用for循环
/*for (int i = 0; i < range; ++i)
{
count[i] = 0;
}*/
for (int i = 0; i < n; ++i)
{
count[a[i] - min]++;
}
//将数据重新写回数组
int j = 0;
for (int i = 0; i < range; ++i)
{

while ((count[i]--) > 0)
{
a[j] = i + min;
j++;
}
}
}


2、基数排序

基数排序是基于数据位数的一种排序算法,什么叫排序位数呢?个,十,百,千,万...明白了吧!。

它有两种算法

①LSD--Least Significant Digit first 从低位(个位)向高位排。

②MSD-- Most Significant Digit first 从高位向低位(个位)排。

排序原理:

因为是按位进行排序,数据可能不统一,有的最大位为十位,有的最大位为百位。我们需要找到最大的位数来进行决定循环的次数。

LSD和MSD的两种算法思想一致,下面以一组数据用LSD进行排序:



在上图中最大的位数为4位,所以我们要对个,十,百,千,四位各进行一次循环排序。下面以个位为例进行示范。

①我们知道无论个十百千万哪个位,都只有10种情况,0-9,所以我们先创建一个大小为10 的哈希表,统计出每个位各有多少数据。例如上图数据中个位为0的出现1次,个位为1的出现2次,个位为2的出现1次,个位为3的出现2次,个位为9的出现1次。所以哈希表创建后得:



②再得出次数之后,我们需要开辟一个辅助空间tmp,并按0~9的顺序将其重新写入辅助空间,但是怎么写呢?这里我们就需要用到矩阵转置的思想了,再开辟一个大小为10 的数组,用来记录每个位(0~9)上的数据重新写入辅助空间时的起始下标。

例如:

0位有1个数据,但是0位之前再其他位,所以0位的起始下标就为0。

1位有2个数据,1位之前有个0位,并且0位有一个数据,所以1位的起始下标为1。

2位有1个数据,2位之前有两个位(0和1),所以2位之前一共有3个数据,所以2位的起始下标就为3。

由上面刻得出,每个位的起始下标就等于每个位之前的总数据个数之和。

所以得出起始下标数组为:



③现在起始下标得到了,我们就可以遍历原数组往辅助空间里写数据了,但是有一点要注意,每写入一个数据,起始下标数组相应位的起始下标就要加1。最后得:



到这里,我们的数据还在辅助空间tmp内,我们需要将其重新写回我们原数据空间。这样我们一趟排序就完成了,其他十位,百位,千位的排序只要在低位排序的基础上进行上面相似的排序即可。

时间复杂度分析:排序最大位数次,每次都要对数组进行一次排序,所以时间复杂度为O(N*最大位数)。

空间复杂度分析:建立与原数组同样大小的辅助空间,再无其他空间开销,所以空间复杂度为O(N)。

实现代码:

//基数排序
//LSD  先以低位排,再以高位排
//MSD  先以高位排,再以低位排
void LSDSort(int *a, int n)
{
assert(a);
//求出其最大位数  个 十 百 千 万....
int digit = 0;
int bash = a[0];
for (int i = 0; i < n; ++i)
{
while (a[i] > (pow(10,digit)))
{
digit++;
}
}
int flag = 1;
for (int j = 1; j <= digit; ++j)
{
//建立数组统计每个位出现数据次数
int Digit[10] = { 0 };
for (int i = 0; i < n; ++i)
{
Digit[(a[i] / flag)%10]++;
}
//建立数组统计起始下标
int BeginIndex[10] = { 0 };
for (int i = 1; i < 10; ++i)
{
BeginIndex[i] = BeginIndex[i - 1] + Digit[i - 1];
}
//建立辅助空间
int *tmp = new int
;
memset(tmp, 0, sizeof(int)*n);//初始化
//将数据写入辅助空间
for (int i = 0; i < n; ++i)
{
int index = (a[i] / flag)%10;
tmp[BeginIndex[index]++] = a[i];
}
//将数据重新写回原空间
for (int i = 0; i < n; ++i)
{
a[i] = tmp[i];
}
flag = flag * 10;
delete[] tmp;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息