算法导论第七章总结:快速排序

来源:互联网 发布:linux shell fork 编辑:程序博客网 时间:2024/06/02 07:28

算法导论第七章总结:快速排序

       对于包含 个数的输入数组来说,快速排序是一种最坏时间复杂度为 Θ(n2) 的排序算法。虽然最坏情况时间复杂度很差,但快速排序通常书实际应用中最好的选择,因为它的平均性能好:期望时间复杂度是 Θ(nlg n),而且 Θ (nlg n)中隐含的常数因子非常小,还能进行原址排序


一、描述 P95~P97

        快速排序也使用分治思想,下面是对数组A[pr]进行快速排序的三步分治过程:

        分解:数组 A[pr] 被划分成两个(可能为空)子数组 A[pq-1] 和 A[q+1…r],使得 A[pq-1] 中的每个元素小于等于 A[q],而 A[q] 也小于等于 A[q+1…r] 中的每个元素。这一步是整个排序算法最关键的步骤。

        解决:通过递归调用快速排序,对子数组 A[pq-1] 和 A[q+1…r] 进行排序。

        合并:因为子数组都是原址排序的,所以不需要合并操作。

        实现

        QUICKSORT (A,p,r)

        if p < r

            q =PARTITION (A, p, r)

            QUICKSORT (A, p, q-1)

            QUICKSORT (A, q+1, r)

 

           为了排序整个数组,初始调用 QUICKSORT (A, 1,A.length)。

           数组的分解是算法的关键部分。

           PARTITION(A,p,r)

           x= A[r]

           i= p-1

           for j = p to r-1  // A[j]是待比较的元素

                ifA[j]≤ x  // 若A[j]比主元小

                    i =i+1   // i往后一位(给要换过来的A[j]空出个位置)

                    exchange A[i] with A[j]   //把A[j]换到新位置,原位置的比x大的换到后面去

           exchange A[i+1] with A[r]      

           return i+1


         PARTITION先选择一个 x =A[r] 作为主元(即数组的最后一个元素),通过与它比较来划分 A[pr],找到主元合适的位置,使比它小的元素在它左边,比它大的元素在它右边。

        随着程序的执行,数组被划分成四个区域。对任意数组下标k,有:

        1. 若pki,则A[k]是小于x的元素。

        2. 若i+1 ≤kj-1,则A[k]是大于x的元素。

        3. 若kj,则A[k]还没被比较。

        4. 若k =r,则A[k]是主元。


        四个区域的划分如图1:


图1   PARTITION过程的4个区域


           PARTITION过程实例如图2:


图2    PARTITION的操作过程


        在PARTITION的最后两行中,通过将主元与最左的大于x的元素进行交换,就可以将主元移到它在数组中的正确位置上,并返回主元的新下标 QUICKSORT 中的 q

        PARTITION在子数组 A[pr] 上的时间复杂度是 Θ (n),其中 n =r-p+1。

二、性能 P97~P99

        快速排序的运行时间依赖于划分是否平衡,而平衡与否依赖于用于划分的元素。若平衡,则快速排序算法性能与归并排序一样。若不平衡,那么快速排序的性能就接近于插入排序了。下面,我们分两种情况进行分析。

        最坏情况划分(不平衡)

        当划分产生的两个子问题分别包含了 n-1 个元素和 0 个元素时,快速排序的最坏情况发生了。假设算法的每次递归调用中都出现这种不平衡划分。划分操作的时间复杂度是 Θ(n)。对大小为0的数组进行递归调用会直接返回,因此 T(0) =Θ (1)。于是算法运行时间的递归式可表示为:T(n) = T(n-1) + T(0) + Θ(n) =T(n-1) + Θ(n)

        其结果为 T(n) = Θ (n2)。        

        最好情况划分(最平衡)

        在可能的最平衡划分中,PARTITION得到的两个子问题的规模都不大于 n/2。这种情况下,快速排序性能非常好,算法运行时间的递归式为:

T(n) = 2T(n/2) +T(0) + Θ(n)

        在上式中,我们忽略了一些余项以及减1操作的影响。该式的解为 T(n) = Θ (nlg n)。      

        平衡划分

        快速排序的平均运行时间更接近于其最好情况,而非最坏情况。理解这一点的关键就是理解划分的平衡性是如何反映到描述运行时间的递归式上的。

       例如,假设算法总是产生9:1的划分,看起来这种划分已经是很不平衡的了。此时,递归式为:

T(n) =T(9n/10) +T(n/10) +cn

       这里,我们显式地写出了Θ(n)中隐含的常数c。图3显示这一递归调用的递归树。



图3    9:1划分的递归树


        递归在深度为处终止。因此,快速排序总代价为 O(n lgn)。由此可见,即使递归的每一层都是9:1这种看起来不平衡的划分,但时间复杂度仍是 O(n lg n)。即使是 99:1 的划分,其时间复杂度仍为 O(n lgn)。事实上,只要划分是常数比例的,算法的运行时间总是 O(n lgn)。

        平均情况下,PARTTION所产生的划分同时混合有“好”和“差”的,两种划分是随机分布的。以图4为例,显示这种情况(第一层是最差划分,第二层是最好划分):


图4    第一层最差,第二层最好的 PARTITION 划分


         一个差的划分后面接着一个好的划分,这种组合产生三个子数组,大小分别为0、(n-1)/2-1 和 (n-1)/2。这一组合的划分代价为 Θ (n) +Θ (n-1)。它的代价与直接一层最好划分是一样的,因为差划分的代价 Θ (n-1) 可以被吸收到好划分的代价 Θ (n) 中去,即 Θ (n) +Θ (n-1) =Θ (n)。因此,好坏划分交替出现时,快速排序的时间复杂度与全是好划分时一样,仍是 O(n lgn)。


三、随机化版本 P100

        讨论快速排序的平均情况性能时,我们假设:输入数据的所有排列都是等概率的,但实际情况并不一定成立,比如插入排序对已基本排好序的数组排序的时间复杂度为O(n),快速排序则需要O(n2)的代价,理由可见此链接:

http://zhidao.baidu.com/link?url=mcDaSKOFqDZT1XA7qA4saw7Q1jxX42P_XWBeotUnaQLwlmEey7eoJVn_VS0Fp8Jg5vpz4FkFpPXSVvnm2yDZ7K

        我们采用随机化的方法,与始终采用 A[r] 为主元的方法不同,随机抽样是从数组 A[pr] 中随机选择一个元素作为主元。为达到这一目的,首先将A[r] 与从 A[pr] 中随机选取的一个元素交换。因为主元元素随机选取,在平均情况下,对输入数组的划分比较均衡。这样可避免对已基本排好序的数组排序时,代价为 O(n2) 的情况。

        代码改动很小,在PARTITION部分加个交换即可,如下:

        RAMDOMIZED-PARTITION (A,p,r)

        i = RAMDOM (p, r)

        exchange A[r] with A[i]

        return PARTITION (A,p,r)

       

       此外,要把 QUICKSORT 中对 PARTITION 的调用改为调用 RAMDOMIZED-PARTITION。

       使用 RAMDOMIZED-PARTITION,在输入元素互异的情况下,快速排序的期望运行时间为O(n lgn),具体见P101~P103。




         






1 0
原创粉丝点击