对于包括n 个元素的输入序列来说,任何比较排序在最坏情况下都要经过nlg(n) 次比较,因此归并和堆排序是渐进最优的,并且任何已知的比较排序最多就是在常数因子上优于它们。而对于非比较排序来说,下界nlg(n) 对它们是没用的,在某些情况下排序的复杂度可以达到 n .
下面介绍其中的一种非比较排序:计数排序
计数排序有一个非常重要的地方就是:假定数据的范围是在某个区间内,如果数据的大小相差非常大的话,那么计数排序是不适用的。比如三个数1,11111111,222222222222,那么在计数排序中开辟的空间消耗和时间消耗是非常不值得的。
说起计数排序感觉这个思想是我所接触的排序思想里面最简单的一个思路。一串无序的输入数据,怎么排序了? 学了比较排序后,正常人的想法都是通过作比较然后交换..........可是换一个思路想,这一串的数据存储在数组中是无序的,可是数组的下标是有序的啊,可不可以利用数组下标一次递增的特点来对数据排序了? 答案肯定是可以的。数组有2个特性一个用来存储值,一个是值的地址,即下标。 那么既然下标可以用来存储地址,那么我们也可以让它来表示数据的值,将2,5,3 分别放在数组下标为2,3,5 上,放好之后我遍历一遍,不就over 了嘛! ok !就是这样的!这就是计数排序的精髓所在。 可是问题来了,如果我有重复的数那该怎么办了?比如说2,2,2,3 一个数组下标只能表示一个值,那么3个相同的数就没法表示了,这里就要引入计数的概念了,3个数那么我在数组为3的下标上存储一个值为3,第一次我遍历到这里之后就将值3 减一,恩, 这是一个挺不错的办法,可是还有一个没考虑到的地方就是,怎么确定数据的位置..........刚才讲到了将每一个数都投射到数组中,那么我如果要求第5 个位置的数在实际的位置的话,就只需要将5之前所有位置的数全加起来就可以了,即把小于这个数的个数都加一遍。上几张图形象化一下概念。
30个的数 假定所有的数的范围都在30以内
在进行和投射和计算之前位置的和之后
之后我们就可以根据原数据进行一一投射了!
#include <iostream> #include <malloc.h> #include <string.h> using namespace std; void CountSort(int a[], int n) { int i, j; int *c = (int *)malloc((n+10)*sizeof(int)); int *b = (int *)malloc((n)*sizeof(int)); // 数组 c 用来保存临时数据 for(i=0; i < n; i++) c[i] = 0; // 将数据投射到数组c 并计算每个数出现的次数 for(i=0; i< n; i++) c[a[i]]++; // 得出比前面小的数有多少个 for(j=1; j < n; j++) c[j] += c[j-1]; // 一一投射 for(i = n-1; i >= 0; i--) { // 下标从0 开始所以先减一 c[a[i]] --; b[c[a[i]]] = a[i]; } for(i = 0; i < n; i++) cout << b[i] << " "; cout << endl; free(c); free(b); } int main() { int a[] = {2,5,3,0,2,3,0,3}; CountSort(a, sizeof(a)/sizeof(int)); system("pause"); return 0; }