您的位置:首页 > 其它

非比较排序---计数排序&基数排序

2016-12-03 10:41 316 查看
如果给出一组数:2, 2, 9, 5, 3, 9,怎样才能遍历一次该数组就能使其有序呢?!试试之前讲的算法,好像都不行。这里我给出另一种排序算法------非比较排序!

非比较排序包括计数排序和基数排序。

一、计数排序

算法思想:

很明显,计数排序当然就需要计数啦,这里就是对这组数中的每个数据进行计数,并以每个数据的相对位置为下标,将统计出来的次数存放在一个count[ ]数组中,并根据count数组还原数组_a。

具体的做法是:选出这组数中的最大值,并用这个值来开辟一个数组count,来存放每个数据出现的次数,统计完后,再重新遍历count,根据count数组中每个下标出现的次数还原原数组_a,count[index]是几就输出几个index。



上边提出了,需要以每个数据的相对位置为下标,想想为什么要用相对位置而不用绝对位置呢?!

现在假如有一组数是:1000,1010,1005,2000,1010,1990,1556,1778,同样,按照上面的思路,首先要选出这组数中的最大值,并用这个数来开辟一个数组。那么问题就来了,这里的最大数是2000,如果按照最大值开辟的环就需要2000个空间,但是仔细观察后发现,这些数都是在1000~2000之间的,也就是说count数组是从下标999的位置开始统计的,这样的话前一半的空间都浪费了。所以这里提出了使用相对位置,同样开辟count大小也需要使用相对大小,即实际只需要开辟(2000-1000+1)个空间。

存在的问题:

上边举得两组例子都是数据相对比较集中的,假如是这样的一组数时:0,222,1000,34,100000,999,1010,3005,这样不仅空间浪费比较严重,另一个缺点是效率比较低,因为数据太分散了,还原数组时需要遍历100000次。所以,计数排序在数据比较集中时是非常快的,此时也不会浪费太多空间。

代码实现:

//计数排序
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);
for (int i=0; i<n; ++i)
{
int index = a[i] - min;	//找出a[i] 在 count[]中的相对位置
count[index]++;
}
//还原排序以后的数组a
int index = 0;
for (int i=0; i<range; ++i)
{
while (count[i])
{
a[index++] = i+min;
count[i]--;
}
}
delete[] count;
}


时间复杂度分析:

首先需要便利一遍原数组_a,时间复杂度为O(N),还原数组_a需要遍历一遍数组count,时间复杂度为O(数据的范围),因此总体的时间复杂度时O(N+数据范围)。

空间复杂度分析:

这里重新开辟了一个数组count,其大小为(最大数-最小数),因此空间复杂度为O(最大数-最小数)

二、基数排序

基数排序又称为"桶子法",与哈希桶有点相似之处。其实现有两种:LSD(低位优先)和MSD(高位优先)。

算法思想:

下面两种实现都以这组数为例:73,22,93,43,55,14,28,65,39,81

LSD:先按低位排序,再按高位排序

具体做法是:

(1)先遍历原数组,按个位数将该数据挂在0-9号桶的某一个下面;

(2)将按照个位排好序的数据收集到tmp数组:需要计算每个桶下面数据存入的起始位置,计算方法是:第i号桶数据的起始位置 = 第(i-1)号桶数据的起始位置 + 第(i-1)号桶的数据的个数,这里与矩阵转置计算起始位置相似《矩阵的的存储及转置算法》

(3)还原数组a:将排好序的数组tmp重新赋给数组a。

(4)再按照十位进行上步骤排序,直到最大数据的每个位都遍历完



MSD:先按高位排序,再按低位排序



代码实现(LSD):

//基数排序
int GetDigit(int* a,int n)	//统计最多的位数
{
int base = 1;
int digit = 0;
for (int i=0; i<n; ++i)
{
while (a[i] >= base)
{
digit++;
base *= 10;
}
}
return digit;
}
void LSDSort(int* a,int n)
{
assert(a);

int digit = GetDigit(a,n);
int* tmp = new int
;	//每次按某个位排序后的数据收集到tmp数组中
int base = 1;
while (digit)
{
//统计0-9号桶中数据出现的次数
int count[10] = {0};
for (int i=0; i<10; ++i)
{
int index = (a[i]/base)%10;
count[index]++;
}

//计算起始位置
int start[10] = {0};
for (int i=1; i<10; ++i)
{
//每个桶中数据的起始位置 = 上一桶数据起始位置 + 上一桶数据的个数
start[i] = start[i-1] + count[i-1];
}

//将数据收集到tmp中
for (int i=0; i<n; ++i)
{
int num  = (a[i]/base)%10;
tmp[start[num]] = a[i];
start[num]++;	//每次写入一个数据后将start中每个桶的起始位置向后挪动一位
}

//将排好一次序的数组重新赋给原来的数组
for (int i=0; i<n; ++i)
{
a[i] = tmp[i];
}

--digit;
base *= 10;
}
delete[] tmp;
}


时间复杂度分析:

每次排序都需要遍历的次数是数据的个数个,共需要排序最大数据的位数位,则时间复杂度是O(N*最多的位数)

空间复杂度分析:

这里开辟了一个tmp来收集每次排序后的数据,其大小是N,因此空间复杂度为O(N)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息