计数排序(counting sort)
计数排序并不基于元素的比较,而是一种利用数组下标来确定元素正确位置的算法。计数排序的核心在于将输入的数据值转化为键值存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序算法的时间复杂度O(n + k)(k为整数的范围)。
j简单描述就是,在一个有确定范围的整数空间中,建立一个长度更大的数组,如当输入的元素是 n 个 0 到 k 之间的整数时,建立一个长度大于等于k的数组。该数组的每一个下标位置的值代表了数组中对应整数出现的次数。根据这个统计结果,直接遍历数组,输出数组元素的下标值,元素的值是几, 就输出几次。
简单实现:
class Solution { public: void coutSort(int* data, int length) { if (data == nullptr || length <= 0) return; //确定数列最大值 int max = data[0]; for (int i = 1; i < length; ++i) { if (data[i] > max) max = data[i]; } // 确定统计数组长度并进行初始化 int* coutData = new int[max + 1]; for (int i = 0; i <= max; ++i) coutData[i] = 0; // 遍历数组,统计每个数出现的次数 for (int i = 0; i < length; ++i) ++coutData[data[i]]; // 排序数组,某个数出现了几次,便在data里累计输出几次 int index = 0; for (int i = 0; i <= max; ++i) { for (int j = 0; j < coutData[i]; ++j) { data[index++] = i; } } } };
优化版(稳定排序):
(1)找出待排序的数组中最大和最小的元素
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
class Solution { public: int* coutSort(int* data, int length) { if (data == nullptr || length <= 0) return nullptr; //确定数列最大值 int max = data[0]; int min = data[0]; for (int i = 1; i < length; ++i) { if (data[i] > max) max = data[i]; if (data[i] < min) min = data[i]; } int d = max - min; // 确定统计数组长度并进行初始化 int* coutData = new int[d + 1]; for (int i = 0; i <= d; ++i) coutData[i] = 0; // 遍历数组,统计每个数出现的次数 for (int i = 0; i < length; ++i) ++coutData[data[i] - min]; // 统计数组做变形,后面的元素等于前面的元素之和 for (int i = 1; i <= d; ++i) coutData[i] += coutData[i - 1]; // 倒序遍历原始数列,从统计数组找到正确的位置,输出到结果数组 int* sortedArray = new int[length]; for (int i = length - 1; i >= 0; i--) { sortedArray[coutData[data[i] - min] - 1] = data[i]; // 找到data[i]对应的coutData的值,值为多少,表示原来排序多少,(因为从1开始,所以减1) coutData[data[i] - min]--; // 然后将相应的coutData的值减1,表示下次再遇到此值时,原来的排序是多少。 } return sortedArray; } };
优化版本的测试:
void test() { int data[] = { 95, 98, 97, 90, 98, 93, 92, 91 }; int length = sizeof(data) / sizeof(int); Solution M; int* array = M.coutSort(data, length); for (int i = 0; i < length; ++i) cout << array[i] << " "; cout << endl; }
局限:
1. 当数组最大和最小值差距过大时,并不适合用计数排序。会造成空间浪费,时间复杂度也会随之升高。
2. 当数组元素不是整数时,如小数, 也不适合用计数排序。