桶排序、计数排序时间复杂度是线性的O(n),因而也叫线性排序,这两个算法是基于非比较的排序算法,不涉及元素之间的比较。
1. 桶排序(Bucket sort)
桶排序的核心思想是把要排序的数据分组放到桶里,然后再把每个桶里的数据单独进行排序,之后把数据取出,组成的序列就是有序的了。主要应用于处理数值分布比较均匀但是数据量很大的场景。相对来说,我们一般使用的排序算法的时间复杂度在nlog(n)到n^2之间,如果n很大时,运算的效率就比较低了,如果我们能把数据分成k组,再单独进行排序,假设原来算法的时间复杂度是O(n^2):
那么拆分到k个桶里之后,时间复杂度就是k*(n/k)^2 = (n^2)/k, 如果k足够大,那么桶排序的时间复杂度就接进O(n).
场景:将超市订单的金额进行桶排序{0,20,10,30,5,22,34,49,46,11,38,6,3}
我们可以分为以下几个桶:
0-9: {0,3,5,6}
10-19:{10,11}
20-29:{20,22}
30-39:{30,34,38}
40-49:{46,49}
放入桶里分别排好序之后取出拼接即可得到有序的序列。
2. 计数排序(Count sort)
计数排序可以说是桶排序的一种特殊情况。比如对某省高考70万考生总分进行排序。假设总分为800分,那么可能的分数为0-800,此时我们将这801种分数分为801个桶,每个桶内的数值都是相同的,可以节约桶内排序的时间,此时依次扫描每一个桶,将考生的分数一次拷贝到新的数据里既可以了。此时算法的时间复杂度就是遍历数据的复杂度,为O(n)。该算法虽然时间复杂度比较低,但是应用并不广泛,因为条件太苛刻了,只能在数据量较大并且数值分布范围较小的情况下应用。
假设我们有序列:a[] = {2,5,3,0,2,3,0,3,3,2,1,5,6},利用计数排序算法从小到大进行排序
首先我们观察到数据的区间范围在0~6之间,那么我们可以建立一个countArr的数值,统计每个数字的大小。
countArr的下标及对应值:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
对应值的个数 | 2 | 1 | 3 | 4 | 0 | 2 | 1 |
然后,我们再统计小于或者等于下标的值有多少个,即:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
小于等于对应值的个数 | 2 | 3 | 6 | 10 | 10 | 12 | 13 |
最后我们将原序列从后往前遍历一遍放入orderArr数组中,从后往前遍历,是为了保证序列的稳定性,往下看实现方法就知道为什么从后往前遍历是稳定的。(所谓稳定性是指序列中有多个相同的数据时,完成排序后,这些相同数据的前后位置保持不变)
首先, a[12] = 6
那么我们将a[12]放入到orderArr数组中,对应的下标是count[6]-1(减一是因为我们统计的是个数,而数组的下标从0开始),然后countArr[6]--。
数组orderArr::
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
数值 |
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
然后看a[11] = 5:
我们将a[11]放入到orderArr数组中,对应的下标是count[5]-1,然后count[5]--;
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
数值 |
|
|
|
|
|
|
|
|
|
|
|
| 5 | 6 |
再进行5次以上运算,可以得到:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
数值 | 0 |
| 1 |
|
| 2 |
|
|
| 3 | 3 |
| 5 | 6 |
我们可以发现,当遍历完整个数组,这个orderArr就会被填充完整,并且已经有序,此时我们只要将数据拷贝到原数组a[]中就可以了。
C语言代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
void countingSort(int a[], int size);
int main()
{
int i=0;
int a[] = {2,5,3,0,2,3,0,3,3,2,1,5,6};
//排序前
for(i=0; i<sizeof(a)/sizeof(int); i++)
{
printf("%d ", a[i]);
}
printf("\n");
//排序
countingSort(a,sizeof(a)/sizeof(int));
//排序后
for(i=0; i<sizeof(a)/sizeof(int); i++)
{
printf("%d ", a[i]);
}
printf("\n");
getchar();
return 0;
}
void countingSort(int a[], int size)
{
int i = 0;
int max = 0;
int *countArr = NULL;//计数数组
int *orderArr = NULL;//临时数组
for(i=0; i<size; i++)
{
if(a[i] > max)
{
max = a[i];
}
}
countArr = (int *)malloc(sizeof(int) * (max+1));
orderArr = (int *)malloc(sizeof(int) * (size));
if((NULL == countArr) || (NULL == orderArr))
{
return ;
}
memset(countArr, 0, sizeof(int) * (max+1));
memset(orderArr, 0, sizeof(int) * (size));
//统计每个值的个数
for(i=0; i<size; i++)
{
countArr[a[i]] += 1;
}
//统计<=下标值的个数
for(i=1; i<size; i++)
{
countArr[i] += countArr[i-1];
}
//为了保证稳定性,从后往前排序
for(i=size-1; i>=0; i--)
{
orderArr[countArr[a[i]]-1] = a[i];
countArr[a[i]]--;
}
//将临时数组的数据拷贝到原数组
for(i=0; i<size; i++)
{
a[i] = orderArr[i];
}
}
如果有误欢迎指正。