[经典算法]计数排序
概述:
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的集合排序时,它的复杂度为Ο(n+k)(其中k是元素的范围),快于任何比较排序算法。
计数排序本质上是通过计算无序集合中元素出现的次数来决定集合应该如何排序的。
例如一个数组{1, 4, 1, 2, 7, 5, 2},进行计数排序过程
1、使用Count数组记录元素次数
Index: 0 1 2 3 4 5 6 7 8 9 Count: 0 2 2 0 1 1 0 1 0 0
2、调整Count数组,统计元素在以排序数组中的偏移
Index: 0 1 2 3 4 5 6 7 8 9 Count: 0 2 4 4 5 6 6 7 7 7
3、反向遍历数据,根据元素的偏移把元素放置到以排序集合中,并减少一次计数。
比如最后一个元素2,对应Count中值Count[2]是4,则在Sorted中第4位置放一个元素2。即Sorted[Count[2]-1] = 2;
Index: 0 1 2 3 4 5 6 7 8 9
Count: 0 2 4 4 5 6 6 7 7 7
Sorted:- - - 2 - - - - - -
基本特性:
1、集合中元素可以转换成整型索引值,且不会冲突。
2、转换的索引值范围已知且有限。
3、时间复杂度为O(n+k),不是基于比较的排序算法,因此效率非常之高。
4、稳定性好
5、需要辅助空间,计数数组和已排序数组
如果索引值可以直接用元素值,并且不需要考虑相同值元素之间的先后,可以节省“以排序数组”所用的空间,优化代码参考后面
程序代码:
#include <gtest/gtest.h> #include <algorithm> using namespace std; // 元素值可以直接用作索引值,并行不考虑相同值元素先后关系 // 这样可以节省以排序数据的辅助空间 void CountingSort2(int* data, int size, int max) { int* counts = new int[max]; memset(counts, 0, max * sizeof(int)); for (int i=0; i<size; ++i) { counts[data[i]]++; } int index = 0; for (int i=0; i<max; i++) { while(counts[i]-- > 0) { data[index++] = i; } } delete[] counts; } // 计数排序 int ConvDataToIndex(float data, int max) { return (int)(data * 10); } template<typename T, typename F> void CountingSort(T* data, int size, int max, F conv) { int* counts = new int[max]; //计数数组 T* sorted = new T[size]; //排序缓存 memset(counts, 0, max*sizeof(int)); for (int i = 0; i<size; ++i) { counts[conv(data[i], max)]++; } for (int i = 1; i<max; ++i) { counts[i] += counts[i-1]; } for (int i = size-1; i >=0; --i ) { int j = --counts[conv(data[i], max)]; sorted[j] = data[i]; } memcpy(data, sorted, size * sizeof(T)); delete[] counts; delete[] sorted; } // 辅助函数 template<typename T> static void ShowElem(T& val) { cout << val << " "; } template<typename T> static bool Validate(T* data, int len) { for (int i=0; i < len-1; ++i) { if (data[i] > data[i+1]) { return false; } } return true; } //测试代码 TEST(Algo, tCountSort) { float d4[] = {0, 1.2, 3.1, 4.0, 1.5, 2.6, 10.4, 9.7, 8.2}; CountingSort(d4, 9, 105, ConvDataToIndex); for_each(d4, d4+9, ShowElem<float>); ASSERT_TRUE(Validate(d4,9)); cout << endl; int d1[] = {2,8,7,1,3,5,6,4}; CountingSort2(d1, 8, 9); for_each(d1, d1+8, ShowElem<int>); ASSERT_TRUE(Validate(d1,8)); cout << endl; int d2[] = {2}; CountingSort2(d2, 1, 3); for_each(d2, d2+1, ShowElem<int>); ASSERT_TRUE(Validate(d2,1)); cout << endl; int d3[] = {2,4,5,6,7,5,2,3,5,7,10,111,2,4,5,6,3,4,5}; CountingSort2(d3, 19, 112); for_each(d3, d3+19, ShowElem<int>); ASSERT_TRUE(Validate(d3,19)); cout << endl; }