计数排序

计数排序的时间复杂度比快速排序,合并排序(O(nlongn))都要好O(n),但是是以空间代价换取的,并且在范围较小的整数数中使用。

在介绍技术排序之前先补充时空权衡的思想。

时空权衡

时空权衡的思想就是以空间资源换取时间效率,在算法设计中经常遇到。

需要注意的是:并不是所有的情况下,时间和空间这两种资源都是相互竞争的。实际上,他们可以联合起来达到最小化。这种情况需要一个高效的数据结构,如:图的深度或广度遍历算法,在邻接矩阵中,时间效率是O(n2 );在邻接链表中,时间效率是O(n+e)。无论是时间角度还是空间角度来看,邻接链表的效率更高,在稀疏图中尤为明显。

时空权衡的思想可以分为以下三点(以及应用算法):

具体思想——>

输入增强:对问题的部分或全部输入做预处理,然后将获得的额外信息进行存储,以加速后面问题的求解。

预构造:使用额外空间来实现更快和更方便的数据存取。与输入增强不同,这种技术只涉及到存取结构。

动态规划:这个策略的基础是把给定问题中重复子问题的解记录在表中。


计数排序

计数排序作为输入增强的例子,其思路是非常简单的:针对待排序列表中的每个元素,算出列表中小于该元素的元素个数,并把结果记录在一张表中。这个“个数”就是该元素在有序列表中的位置。(下面几种方法的不同就在于这个“个数”是如何得到的)如对某个元素来说,这个个数是10,它应该排在有序数组第11个位置(如果从0开始计数,则下标是10)。

几个计数排序的比较——>

  • 通用的计数排序:时间O(n);空间O(n+k),k是最大元素值
  • 比较计数排序:时间O(n2 );空间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(n2 )。

例子——>

数组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;

参考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章