计数排序和基数排序
2017-08-10 20:22
183 查看
排序总归来说可分为两大类,比较排序与非比较排序。比较排序就是我们常用到的冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序,归并排序。非比较排序不常用,但是在对一些特殊的情况进行处理时,它的速度反而更快。
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++代码实现:
[cpp] view plain copy print?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
d222
进行排序:
在上图中最大的位数为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)。
实现代码:
[cpp] view plain copy print?//基数排序
//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;
}
}
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++代码实现:
[cpp] view plain copy print?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++;
}
}
}
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
d222
进行排序:
在上图中最大的位数为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)。
实现代码:
[cpp] view plain copy print?//基数排序
//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;
}
}
//基数排序 //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; } }
相关文章推荐
- 计数排序和基数排序
- 三种线性的非基于比较的排序算法:计数排序、桶排序与基数排序
- 基于非比較的排序:计数排序(countSort),桶排序(bucketSort),基数排序(radixSort)
- 由Maximum Gap,谈桶排序,基数排序和计数排序
- 【数据结构】非比较排序算法(实现计数排序和基数排序)
- 计数排序、桶排序和基数排序
- 基数排序和计数排序
- 基于非比较的排序:计数排序(countSort),桶排序(bucketSort),基数排序(radixSort)
- 计数排序、桶排序和基数排序
- 计数排序、桶排序和基数排序
- 三种线性排序算法:计数排序、桶排序与基数排序
- 计数排序和基数排序
- 计数排序和基数排序
- 【算法导论】线性时间排序-计数排序、基数排序及桶排序
- 计数排序,基数排序和桶排序
- 计数排序和基数排序
- 非比较排序——计数排序和基数排序
- 非比较排序之 计数排序与基数排序
- 三种线性排序算法 计数排序、桶排序与基数排序
- 线性排序:计数排序与基数排序