【算法导论】8.线性时间排序(计数排序,基数排序,桶排序)

三种线性时间复杂度的排序算法:计数排序,基数排序,桶排序。


8.1排序算法的下界

给定两个元素ai和aj,如果使用比较排序,可以有ai<aj,ai<=aj,ai=aj,ai>aj,ai>=aj五种操作来确定其相对次序。本节假设所有的比较采用ai<=aj形式。

决策树模型:

决策树是一颗完全二叉树,其叶结点列出了所有比较的可能。在下图决策树中,表示对于三个元素的排序,存在<1,2,3>,<1,3,2>,<3,1,2>,<2,1,3>,<2,3,1>,<3,2,1>这六种,其中1,2,3表示元素的下标。例如<1,3,2>表示a1<=a3<=a2。排序算法的执行对应于一条从树的根结点到叶结点的路径。

在决策树中,从根结点到任意一个可达叶结点之间最长简单路径的长度,表示的是对应的排序算法中最坏的情况下的次数。因此,一个比较排序算法中最坏情况的比较次数是树的高度。所以可以给出一个下界:任何比较排序算法都需要做\Omega(nlgn)次比较。

堆排序和归并排序都是渐进最优的比较排序算法。


8.2计数排序

计数排序假设n个输入元素中的每一个都是在0到k区间的一个整数,其中k为某个整数。当k=O(n)时,排序的运行时间为\Theta(n)。

思想:对每一个输入元素x,确定小于x的元素个数。利用这个信息直接把x放在它输出数组的位置上。如果有几个相同元素时,需要做出修改。

代码:假设输入为A[1..n],A.length=n。B[1..n]存放排序的输出,C[0..k]提供临时存储空间。

Counting-Sort(A,B,k){
    let C[0..k] be a new array;
    for i=0 to k
    {
        C[i]=0;//初始化数组C并置为0
    }
    for j=1 to A.length
        C[A[j]]=C[A[j]]+1;//C中保存等于i的元素的个数
    for i=1 to k
        C[i]=C[i]+C[i-1];//C中保存小于或等于i的元素个数
    for j=A.length downto 1
    {
        B[C[A[j]]]=A[j];//把A中的元素放到B中正确的位置
        C[A[j]]--;//每放好一个需要减1,因为如果出现了值相同的多个元素,不能放在同一个位置,减1后就放在了前一个位置
    }
}

总的运行时间代价O(k+n)。在实际中,当k=O(n),一般会采用基数排序,这时运行时间为\Theta(n)。计数排序是稳定的(所以可以用于基数排序的子过程)。


8.3基数排序

基数排序是先按最低有效位进行排序来解决卡片排序问题。然后算法将所有卡片合并成一叠。重复对d位数字都进行排序,此时所有的卡片已按d位数完全排好序。对这一叠卡片的排序仅需要进行d轮(d为位数)。为了确保基数排序的正确性,一位数排序算法必须是稳定的,即保证排序过程以及从容器中取出时不改变顺序。

假设n个d位的元素存放在数组A中,其中第1位是最低位,第d位是最高位:

Radix-Sort(A, d)
{
    for i=1 to d
        use a stable sort to sort array A on digit i;
}

这个排序算法非常直观,就是分别对每一位使用稳定的排序算法排好。

给定n个d位数,其中每一个数位有k个可能的取值。如果Radix-Sort使用的稳定排序算法耗时\Theta(n+k),那么它可以在\Theta(d(n+k))时间内将这些数排好序。当d为常数且k=O(n)时,基数排序具有线性的时间代价。

给定n个b位数和任何正整数r<=b,如果Radix-Sort使用的稳定排序算法对数据的取值区间是0到k 的输入进行排序耗时\Theta(n+k),那么它就可以在\Theta((b/r)(n+2^r))时间内将这些数排好序。


8.4桶排序

桶排序假设输入数据服从均匀分布,平均时间代价为O(n)。

计数排序与桶排序的区别:计数排序假设输入的数据都属于一个小区间内的整数,桶排序假设输入是由一个随机过程产生,该过程将元素均匀、独立地分布在[0,1)区间上。它们的主要区别就是用于排序的数据服从不同的分布。

将[0,1)区间划分为n个相同大小的子区间,或称为桶。将n个输入数分别放到各个桶中。因为输入数据是均匀、独立分布在[0,1)区间上,所以一般不会出现很多数落在同一个桶的情况。为了得到输出结果,我们先对每个桶中的数进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来。

假设输入为有n个元素的数组A,每个元素A[i]满足0<=A[i]<1。临时数组B[0..n-1]用于存放链表(即桶),并假设存在一种用于维护这些链表的机制:

Bucket-Sort(A)
{
    n=A.length;
    let B[0..n-1] be a new array;
    for i=0 to n-1
        make B[i] an empty list//初始化一个空数组B
    for I=1 to n
        insert A[i] into list B[nA[i]];//A是[0,1)的分布,乘n后向下取整放进B中
    for I=0 to n-1
        sort list B[i] with insertion sort
    concatenate the list B[0],B[1],...,B[n-1] together in order//取出排序后的元素
}

运行的时间期望为\Theta(n),即使输入数据不服从均匀分布,桶排序也仍然可以在线性时间内完成。

猜你喜欢

转载自blog.csdn.net/Chen_Swan/article/details/85921757