基本原理
计数排序的基本思想就是:加入输入一个数x,如果我们可以找到比x小的数有几个,那么就可以直接将x放入到对应的输出数组的位置。
比如输入一个数x=12,发现在输入的数据中,比12小的有4个,那么毫无疑问12就该排在第五位。
通过上述的描述,可能你会发现,诶那每次我要找到有几个数比12小,不都是要把数组遍历一遍吗?n个数的话就会需要O(n^2),不是慢到怀疑人生吗?
现在就来说一下怎么进行计数排序,首先需要三个数组:
输入数组A,输出数组B(就是保存将A进行排序后的数组),提供中间存储的数组C
现在假设输入数组A={2,5,3,0,2,3,0,3},其中最大的数为5,则令k=5;
那么数组C的大小就为K+1,全部初始化为0;
for i=0 to A.length
C[A[i]]+=1;
上面这步的目的是统计在A的中每个数出现了几次,然后将这个数作为C数组的下标,出现的次数作为对应下标里面的值。
经过上面这步,C={2,0,2,3,0,1}
还记得在之前说的吗,计数排序是基于“计数排序的基本思想就是:加入输入一个数x,如果我们可以找到比x小的数有几个,那么就可以直接将x放入到对应的输出数组的位置。”,接下来这步就是来统计有几个数比X小。
for j=1;j<C.length;j++
C[j]=C[j]+C[j-1]
经过这步C={2,2,4,7,7,8}
通过C数组我们就可以知道5应该排在第8位。这里你可能会对C数组产生疑问,别急,接下去看。
现在就是用到B数组的时候了
for j=A.length-1 to 0
C[A[j]]-=1;
B[C[A[j]]]=A[j];
B={0,0,2,2,3,3,3,5}
总的来说就是首先通过遍历一遍A数组,统计出A中每个数出现的次数得到C数组,然后通过C数组的C[i]=C[i]+C[i-1],可以知道每个数有几个数比自己小,也就是A中的每个数该排在哪个位置。这里出现重复的数,通过C[A[i]]-=1;自动处理了。
伪代码
来自《算法导论》
时间复杂度
从上面的伪代码看,第一个for循环时间复杂度是O(k),第二个是O(n),第三个是O(k),第四个是O(n),所以总的是O(k+n),特别当n==k的时候,时间复杂度是O(n)。
计数排序不需要比较操作,也不需要交换操作,是一种简单的排序方式,但是这是一种空间换时间的排序方式,类似的空间换时间的排序还有桶排序等。
特别的当O(k)>=O(nlogn)的时候,计数排序就不那么有效了。
java代码实现
public class CountingSort {
public static void main(String[] args) {
int[] A = {2,5,3,0,2,3,0,3};
int[] B = CountSort(A);
for(int i:B)
System.out.print(i+" ");
}
public static int[] CountSort(int[] A){
int[] B = new int[A.length];
int max=A[0], min=A[0];
for(int i:A){
if(i>max)
max=i;
if(i<min)
min=i;
}
int[] c =new int[max-min+1]; //对C数组的大小进行优化
for(int i:A){
c[A[i]-min]+=1;
}
for(int j=1;j<c.length;j++)
c[j]=c[j]+c[j-1];
for(int j=A.length-1;j>=0;j--){
c[A[j]-min]-=1;
B[c[A[j]-min]]=A[j];
}
return B;
}
}
参考资料:
《算法导论》