排序算法的下界和如何超越下界(摘自算法基础)

来源:互联网 发布:php mysql 事务处理 编辑:程序博客网 时间:2024/06/06 13:07

在一些特定的规则下,排序的时间复杂度不能超越ø(nlgn)

然而,计数排序基数排序打破了规则的约束,仅仅在ø(n)时间内就能够实现对数组的排序。


背景:

§ 基于排序的规则:

选择,插入,归并,快速排序算法,在确定排序顺序时都仅仅依赖于对排序关键字对进行比较,它们确定决策时的依据均是”如果这个元素的排序关键字比另一个元素的排序关键字小,那么就进行相应操作,否则,进行其他操作或者什么也不做”

§ 基于比较排序的下界:

比较排序定义为仅仅通过比较元素对来确定排序顺序,

Ω符号给出了下界,因此称“对于任意大的n,任何比较排序算法在最坏情况下至少需要cnlgn次比较操作(对于某个常量c成立)”由于每次比较至少需要花费常量时间,对于n个元素的基于比较的排序操作,能够得出一个时间为Ω(nlgn)的下界。

关于此下界的说明:

1.它仅仅指最坏情况。在最坏情况下,该排序必定需要Ω(nlgn)次比较操作。

2.Ω(nlgn)这一下界不依赖于任何算法,只要该算法是一个比较排序。


使用计数排序超越下界:

假设我们知道排序关键字是0到m-1范围内的整数,并且假定我们知道刚好有3个元素的排序关键字等于5并且刚好有6个元素的排序关键字小于5,那么可得,在排好序的数组中,排序关键字等于5的元素应该位于位置7,8,9上。更一般地讲,如果已知有k个元素的排序关键字等于x,而l 个元素的排序关键字小于x,那么可得,排序关键字等于x的元素应该位于位置l+1到l+k之间。因此,我们需要计算出对于每个可能的排序—关键字值,有多少个元素的排序关键字小于那个值,又有多少个元素的排序关键字等于那个值。

首先通过计算出有多少个元素的排序关键字等于某个值,随后就能计算出有多少个元素的排序关键字小于每个可能的排序—关键字值。

程序 COUNT-KEYS-EQUAL(A,n,m)

输入 A:一个数组(数组内元素取值范围为介于0到m-1之间的整数)

n:数组A中的元素个数

m:定义了数组A中元素的取值范围

输出 一个数组equal[0......m-1](equal[j]表示数组A中元素值等于j的元素个数,j=0,1,2,...,m-1)。

步骤

1.创建一个新数组equal[0......m-1]。

2.令equal数组中的每个元素值均为0。

3.令i从1到n依次取值

A.key被赋值为A[i]。

B.将equal[key]的值自增一。

4.返回equal 数组。

<步骤2执行了m次迭代,步骤3执行了n次迭代,并且每次循环的每次迭代均会耗费常量时间,COUNT-KEYS-EQUAL花费的时间为ø(m+n)。如果m是一个常数,那么COUNT-KEYS-EQUAL花费的时间为ø(n)。

接着通过equal数组的使用,得出对于每一个值,有多少个元素的排序关键字小于该值。

程序 COUNT-KEYS-LESS(equal, m)

输入 equal:由COUNT-KEYS-EQUAL返回的数组。

m:定义了equal数组中的索引的取值范围:0~m-1。

输出 一个数组less[0......m-1](对于j=0,1,2,...,m-1,less[j]=equal[0]+equal[1]+...+equal[j-1])。

步骤

1.创建一个新数组less[0......m-1]。

2.将less[0]赋值为0。

3.令j从1到m-1依次取值:

A.将less[j]赋值为less[j-1]+equal[j-1]。

4.返回less数组。

很容易得出COUNT-KEYS-LESS程序能在ø(m)时间内运行完成,并且它一定不会对排序关键字进行比较。

接着可以创建一个排好序的数组,尽管并非原址排序:

程序 REARRANGE(A,less,n,m)

输入 A:一个数组(数组内元素取值范围为介于0到m-1之间的整数)

n:数组A中的元素个数

m:定义了数组A中元素的取值范围

less:由COUNT-KEYS-LESS返回的数组

输出 一个数组B(包含数组A中的元素,并且是排好序的)

步骤

1.创建新数组B[1...n],next[0...m-1]

2.令j从0到m-1依次取值:

A.将next[j]赋值为less[j]+1

3.令i从1到n依次取值:

A.将key赋值为A[i]

B.将index赋值为next[key]

C.将B[index]赋值为A[i]

D.将next[key]自增一

4.返回数组B

该思想如下,从左到右检查数组A时,next[j]指出了原本在数组A中的下一个值为j的元素应该存放在数组B中的索引位置。

第二步的循环首先确定了next数组的元素值,next[j]=l+1,其中l=less[j]。

第三步的循环从左到右仔细检查了数组A的值。对于每个元素A[i],第3A步将A[i]存储在key中,第3B步中计算出A[i]应该存放在数组B中的索引位置index,第3C步将A[i]移动到数组B中的这一索引位置上。因为数组A中的下一元素应该存放在数组B的下一个位置上,所以第3D步将next[key]值自增一。

将上面三个程序合并,形成计数排序(counting sort):

程序 COUNTING-SORT(A,n,m)

输入 A:一个数组(数组内元素取值范围为0~m-1)

n:数组A中的元素个数

m:定义了数组A中元素的取值范围

输出 一个数组B(包含数组A中的元素,并且是排好序的)

步骤

1.调用COUNT-KEYS-EQUAL(A,n,m),将该调用返回的结果赋值给数组equal。

2.调用COUNT-KEYS-LESS(equal, m),将该调用返回的结果赋值给数组less。

3.调用REARRANGE(A,less,n,m),将该调用返回的结果赋值给数组B。

4.返回数组B。

计数排序能够超越额比较排序的下界Ω(nlgn),因为它从来不会对排序关键字进行比较。反之,它将排序关键字作为数组的索引,能进行这样的操作是因为排序关键字均是非常小的整数,如果排序关键字是带有分数的实数,或者是字符串,那么我们就不能使用计数排序了。

计数排序还有另外一个特性:它是稳定的。在一个稳定的排序中,带有相同排序关键字的元素在输出数组中输出的次序与它们在输入数组中的排序一致,即将输入数组中的第一个元素放置在输出数组中的第一个位置上。

运行时间为ø(n),因为对于n个学生的成绩进行排序时,成绩取值为0~100,m=101是个常量(注意,待排序关键字的变化范围是0~m-1)


基数排序:

基数排序算法中,我们假定将每个排序关键字看作是d位数字,其中每一位取值为0~m-1,我们自右向左地对每一位上的数字进行稳定排序,如果使用计数排序作为这一稳定排序算法,每位上的排序时间为ø(m+n),那么对d位进行排序的总时间为ø(d(m+n))。如果m是一个常量,那么基数排序的时间为ø(dn)。如果d也是一个常量,那么基数排序的时间仅仅为ø(n)。

当基数排序使用计数排序来对每一位进行排序时,它从来不会对两个排序关键字进行比较。它将每一位看作是计数排序中数组的索引。所以基数排序和计数排序能后超越比较排序的下界Ω(nlgn)。


0 0