前面提到的排序(选择排序、插入排序、归并排序、快速排序、堆排序等)都是比较排序。也就是说,以上排序实现的思路都是需要元素的比较才能实现。换一种思路,是不是元素排序必须需要数组内的元素之间的比较呢?其实不然,今天介绍的计数排序就不是利用比较,而是巧妙的利用了下标和元素的关系进行排序,把时间转换成空间,典型的时空变换思路。
在这个思想中,需要维护三个数组a[],b[],c[],数组a是原始的输入数组,数组b是排序后需要输出的数组,数组c是计数数组。这就是所需要的空间。
首先,初始化计数数组c。那么到底我们需要维护一个多大的计数数组呢?这就要求我们必须知道原始输入数组中的最大值。开辟一个大小为原始输入的最大值的数组,并且c中每个元素都初始化为0。为什么要这么做呢。因为数组c的下标即为原始值,每个下标所对应的数值即为此数值的个数。例如c[5] = 10,就表示a中有10个5。
其次,就是对数组c中的数值进行修改。即c[a[i]]++。循环a中的所有元素,把每个c[a[i]]自增1。这个不难理解。就是统计每个元素的个数。
然后就是关键的第一步,c[i] = c[i] + c[i-1]。这句代码的意思就是,比原始值i小的数值个数是c[i]+ c[i-1]。这个开始有点绕,不妨这样想一下:c中第i个数就是原始i的个数,前面所有的(0到i-1)都是比i小的数,把他们都加起来就是所有比i小的数值的个数。这句话就是完成这么一个功能。为什么要实现这么一个功能呢?接下来就是关键的第二步,排序。
最后这一步排序其实已经很明白了。我已经知道比我小的数的个数了,那我就应该直接放在那些个数的后面一个不就完了?确实是这样。也就是b[c[a[i]] - 1] = a[i](考虑到了数组下标为0所以减1)。正因为如此,简化了时间复杂度。这样其实还没有完,还有一个十分重要的步骤,那就是把所对应的c中的数值(计数个数)减一,因为你已经把一个 i 放进b里面了。
具体C++代码如下,已经在CB完美运行。
1 #include <iostream> 2 using namespace std; 3 4 int c[100]; 5 void countingSort(int a[], int b[], int max, int len) { 6 for(int i = 0; i <= max; ++i){ 7 c[i] = 0; 8 } 9 10 for(int i = 0; i != len; ++i){ 11 c[a[i]] = c[a[i]] + 1; 12 } 13 14 for(int i = 1; i <= max; ++i){ 15 c[i] = c[i] + c[i - 1]; 16 cout<<c[i]<<endl; 17 } 18 19 for(int i = len-1; i >= 0; --i){ 20 b[c[a[i]] - 1] = a[i]; 21 c[a[i]] = c[a[i]] - 1; 22 } 23 } 24 25 int main() { 26 int a[8] = {2,5,3,0,2,3,0,3}; 27 int b[8]; 28 int max = 5; 29 int len = sizeof(a)/sizeof(int); 30 countingSort(a, b, max, len); 31 32 for(int i = 0; i < 8; ++i) 33 cout<<b[i]<<" "; 34 35 return 0; 36 }