计数排序的时间复杂度比快速排序,合并排序(O(nlongn))都要好O(n),但是是以空间代价换取的,并且在范围较小的整数数中使用。
在介绍技术排序之前先补充时空权衡的思想。
时空权衡
时空权衡的思想就是以空间资源换取时间效率,在算法设计中经常遇到。
需要注意的是:并不是所有的情况下,时间和空间这两种资源都是相互竞争的。实际上,他们可以联合起来达到最小化。这种情况需要一个高效的数据结构,如:图的深度或广度遍历算法,在邻接矩阵中,时间效率是O(n 2 2 );在邻接链表中,时间效率是O(n+e)。无论是时间角度还是空间角度来看,邻接链表的效率更高,在稀疏图中尤为明显。
时空权衡的思想可以分为以下三点(以及应用算法):
- 输入增强:计数法排序,字符串匹配的Horspool算法(最坏时间O(nm),平均O(n))、Boyer-Moore算法(最坏O(n+m)))http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
- 预构造:散列表,B树索引
- 动态规划
具体思想——>
输入增强:对问题的部分或全部输入做预处理,然后将获得的额外信息进行存储,以加速后面问题的求解。
预构造:使用额外空间来实现更快和更方便的数据存取。与输入增强不同,这种技术只涉及到存取结构。
动态规划:这个策略的基础是把给定问题中重复子问题的解记录在表中。
计数排序
计数排序作为输入增强的例子,其思路是非常简单的:针对待排序列表中的每个元素,算出列表中小于该元素的元素个数,并把结果记录在一张表中。这个“个数”就是该元素在有序列表中的位置。(下面几种方法的不同就在于这个“个数”是如何得到的)如对某个元素来说,这个个数是10,它应该排在有序数组第11个位置(如果从0开始计数,则下标是10)。
几个计数排序的比较——>
- 通用的计数排序:时间O(n);空间O(n+k),k是最大元素值
- 比较计数排序:时间O(n 2 2 );空间O(n)
- 分布计数法排序:时间O(n)
通用的计数排序
- 列表元素个数为n,最大元素值为k
- 开辟数组count[k]用于记录元素的个数
- 开辟数组temp[n]用于存储有限列表
- 场合:适用于个数较多,但元素大小范围较小的整数列表
代码——>
public class GeneralCountingSort {
public static void main(String[] args) {
test();
}
/**
*
* @param array正整型待排序数组
* @return
*/
public static int[] conutsorting(int[] array) {
//数组长度
int n = array.length;
//元素最大值,数组范围
int k = 0;
for (int i = 0; i < n; i++) {
k = array[i] > k ? array[i] : k;
}
//记录元素个数
int[] count = new int[k+1];
for (int i = 0; i < n; i++) {
count[array[i]]++;
}
//元素按个数累加得到序号,即小于该元素的“个数”
for (int i = 1; i <= k; i++) {
count[i] += count[i-1];
}
//排序
int[] temp = new int[n];
for (int i = 0; i < n; i++) {
count[array[i]]--;//①相同元素放置②下标从0开始
temp[count[array[i]]] = array[i];
}
return temp;
}
/**
* 测试
*/
public static void test() {
int[] array = {7, 4, 2, 1, 5, 3, 1, 5};
int[] result = conutsorting(array);
for (int i = 0; i < result.length; i++) {
System.out.print(result[i] + " ");
}
}
}
比较计数排序
比较计数排序开辟空间为O(n),所以这里的计数方式与通用计数排序不同,仍要依次比较后才记录,时间复杂度为O(n 2 2 )。
例子——>
数组A[0..5]: | 62 | 31 | 84 | 96 | 19 | 47 |
---|---|---|---|---|---|---|
初始记录数组count[]: | 0 | 0 | 0 | 0 | 0 | 0 |
i=0时 | 3 | 0 | 1 | 1 | 0 | 0 |
i=1时 | 1 | 2 | 2 | 0 | 1 | |
i=2时 | 4 | 3 | 0 | 1 | ||
i=3时 | 5 | 0 | 1 | |||
i=4时 | 0 | 2 | ||||
最终状态 | 3 | 1 | 4 | 5 | 0 | 2 |
数组S[0..5]: | 19 | 31 | 47 | 62 | 84 | 96 |
伪代码——>
ComparisonCountingSort(A[0..n-1])
//用比较计数法对数组排序
//输入:待排序数组A[0..n-1]
//输出:将A中的元素按升序排列的数组S0..n-1]
for(i = 0; i <= n-1; i++) do
Count[i] ← 0;
for(i = 0; i <= n-2; i++) do
for(j = i+1; j <= n-1; i++) do
if(A[i] < A[j])
Count[j] ← Count[i] + 1;
else
Count[i] ← Count[i] + 1;
for(i = 0; i <= n-1; i++) do
S[Count[i]←A[i]];
return S;
分布计数法排序
思想:如果带排序的元素的值都来自于一个已知的小范围,即元素的值位于下界l和上界u之间的整数,可以计算元素出现的频率,记录在F[0..u-l]中,这样前F[0]个位置填入l,接着F[1]个位置填入l+1,以此类推。
例子——>
待排序数组:13 11 12 13 12 12
数组值 | 11 | 12 | 13 |
---|---|---|---|
频率 | 1 | 3 | 2 |
分布值 | 1 | 4 | 6 |
处理过程:D[0..2]表示分布值,从右往左更容易(即从12开始)
________ ___D[0..2]___ ___S[0..5]___
A[5] = 12 1 4* 6 - - - 12 - -
A[4] = 12 1 3* 6 - - 12 - - -
A[3] = 13 1 2 6* - - - - - 13
A[2] = 12 1 2* 5 - 12 - - - -
A[1] = 11 1* 1 5 11 - - - - -
A[0] = 13 0 1 5* - - - - 13 -
伪代码——>
DistributionCountingSort(A[0..n-1],l,u)
//分布计数法,对来自于有限范围整数的一个数组进行排序
//输入:数组A[0..n-1],数组中的整数位于l和u之间(l<=u)
//输出:A中元素构成的非降序数组S[0..n-1]
for(j = 0; j <= u-l; j++) do D[j] ← 0;//初始化频率数组
for(i = 0; i <= n-1; i++) do D[A[i]-l] ← D[A[i]-l] + 1;//计算频率值
for(j = 0; j <= u-l; j++) do D[j] ← D[j-1] + D[j];//重用于分布
for(i = n-1; i >= 0; i--) do
j ←A[i]-l;
S[D[j]-1] ← A[i];
D[j] ← D[j] - 1;//分布值减1,得到下个相同元素
return S;
参考:
- 《算法设计与分析基础》
- 计数排序详解