《算法导论3rd第七章》快速排序

前言

快速排序是用于排序的最佳实用选择(jre中默认)。它最坏情况时间复杂度为𝑄(𝑛2)的排序算法,但它的平均性能非常好,期望时间复杂度为𝑄(𝑛𝑙𝑜𝑔𝑛),并且𝑛𝑙𝑜𝑔𝑛中隐含的常数因子非常小

快速排序的分治过程

快速排序使用了分治的思想。下面是对一个典型的子数组𝐴[𝑝,𝑟]进行快速排序的三步分治过程:

  • 分解:数组A[p,r]被划分为两个(可能为空)子数组𝐴[𝑝…𝑞−1]和𝐴[𝑞+1…𝑟],使得𝐴[𝑝…𝑞−1]中的每一个元素都小于等于𝐴[𝑞],而𝐴[𝑞]也小于等于𝐴[𝑞+1…𝑟]中的每一个元素。其中计算下标𝑞也是划分过程的一部分。
  • 解决:通过递归调用快速排序,对子数组𝐴[𝑝…𝑞−1]和𝐴[𝑞+1…𝑟]进行排序
  • 合并:因为子数组都是原址排序的,所以不需要排序操作:数组𝐴[𝑝…𝑟]已经有序。
QuickSort(A,p,r)
if	p < r
    q = Partition(A,p,r)
    QuickSort(A,p,q-1)
    QuickSort(A,q+1,r)

Partition(A,p,r)
x = A[r]
i = p-1
for j = p to r-1
	if A[j] <= x
		i = i + 1
		exchange A[i] with A[j]
exchange A[i+1] with A[r]
return i + 1    

在Partition第4-7行循环体的每一轮迭代开始时,对于任意数组下标𝑘有

  1. 若𝑝≤𝑘≤𝑖,则𝐴[𝑘]≤𝑥。
  2. 若𝑖+1≤𝑘≤𝑗−1,则𝐴[𝑘]>𝑥。
  3. 若𝑘=𝑟,则𝐴[𝑘]=𝑥。
    在这里插入图片描述

在子数组𝐴[𝑝…𝑟]中,Partition维护了四个区域。

  1. 𝐴[𝑝…𝑖]区间内所有值都小于等于𝑥,
  2. 𝐴[𝑖+1…𝑗−1]区间内的所有值都大于𝑥,
  3. 𝐴[𝑟]=𝑥。
  4. 𝐴[𝑗…𝑟−1]中的末处理

练习

在这里插入图片描述

1-1

蓝色部分代表不大于pivot,红色部分表示大于pivot
在这里插入图片描述

1-2

当所有的元素都相同的时候q=r,这是因为该算法结束后有 a q < a i ,   ( q < i ≤ r ) a_q \lt a_i, \ (q \lt i \le r) aq<ai, (q<ir),所以没有任何元素会在A[q]之后。将算法变成交替地将等于pivot的元素放到大小两个集合中,这样就能使得 q = ⌊ ( p + r ) / 2 ⌋ q=\lfloor (p+r)/2 \rfloor q=(p+r)/2

Partition(A, p, r)
    x = A[r]
    f = 0
    i = p - 1
    for j = p to r - 1
        if x > A[i] or (f > 0 and x == A[i])
            i = i + 1
            exchange A[i] with A[j]
        f = f xor 1 //异或操作
    exchange A[i + 1] with A[r]
    return i + 1
1-3

由于j从p变为r-1,而循环内的操作运行时间都与输入规模无关为O(1),循环共进行r-p次,所以总的时间复杂度为O(r-p)=O(n)。

1-4

只要把比pivot小的元素放到后边,把比pivot大的元素放在前边即可。

Partition(A, p, r)
    x = A[r]
    i = p - 1
    for j = 1 to r - 1
        if A[i] > x
            i = i + 1
            exchange A[i] with A[j]
    exchange A[i + 1] with A[r]
    return i + 1

快速排序的性能

快速排序的运行时间依赖于划分是否平衡,二平衡与否又依赖于划分的元素

  • 最坏情况划分:当划分的两个子问题分别包含了𝑛−1个元素和0个元素时,递归式为 𝑇(𝑛)=𝑇(𝑛−1)+Θ(𝑛) , 得𝑇(𝑛)=Θ(𝑛2) 。
  • 最好情况划分:在可能的最平衡划分中,Partition得到的两个子问题的规模都不大于𝑛/2。递归式为𝑇(𝑛)=2𝑇(𝑛/2)+Θ(𝑛) ,得 𝑇(𝑛)=Θ(𝑛𝑙𝑜𝑔𝑛)
  • 平衡的划分:快速排序的平均运行时间更接近于其最好情况,假设划分比例为9:1,递归式为𝑇(𝑛)=𝑇(9𝑛/10)+𝑇(1𝑛/10) +Θ(𝑛) ,得 𝑇(𝑛)=Θ(𝑛𝑙𝑜𝑔𝑛)

事实上,任何一种常数比例的划分都会产生深度为Θ(𝑙𝑜𝑔𝑛)的递归树,其中每一层的时间代价都是𝑂(𝑛)。因此只要划分是常数比例的,算法的运行时间总是𝑂(𝑛𝑙𝑜𝑔𝑛)。

练习

在这里插入图片描述

2-1

假设T(n)=Ο(n^2),即存在 T(n)<= c n 2 cn^2 cn2
在这里插入图片描述

2-2

当数组A所有元素相同时,QUICKSORT中的划分时极为不平衡的,n-1:0的划分,T(n)=T(n-1)+Θ(n)解这个递归式T(n)=Θ(n^2)

2-3

按照降序排序时,在QUICKSORT中的划分时极为不平衡的,n-1:0的划分,所以其时间复杂度为T(n)=T(n-1)+Θ(n)解这个递归式 T(n)=T(n)=Θ(n^2)

2-4
  • 插入排序在基本有序的情况下,基本无需移动任何元素来插入,所以只有外层循环了n次,所以时间复杂度为O(n)
  • 快速排序在基本有序的情况下,在划分数组时,划分得非常不平衡,那么其时间复杂度是O(nlgn),而达到完全有序时,时间复杂度达到O(n^2)
2-5
  1. a<=1/2 => a<1-a
  2. 最小深度达成条件全是a这边分裂k次
  3. 假设数组长度为n,即 n * a^k = 1
  4. 最小深度k = -lgn/lga
  5. 最大深度k=-lgn/lg(1-a)
2-6
  1. 当仅仅当, 划分比在 [a,1/2]区间的时候,更平衡
  2. (1/2-a)/1/2 = 1-2a

快速排序的随机化版本

在讨论快速排序的平均情况时,我们的假设前提是:输入数据的所有排列都是等概率的。但是在实际中,这个假设不会总成立。
采用随机抽样(random sampling)的随机化技术。随机抽样是从子数组𝐴[𝑝…𝑟]中随机选择一个元素作为主元。为达到这一目的,首先将𝐴[𝑟]与从𝐴[𝑝…𝑟]中随机选择的一个元素交换位置。

Randomized_QuickSort(A,p,r)
if	p < r
	q = Randomized_Partition(A,p,r)
	Randomized_Partition(A,p,q-1)
	Randomized_Partition(A,q+1,r)


Randomized_QuickSort(A,p,r)
i = Random(p,r)
exchange A[r] with A[i]
return Partition(A,p,r)

我们可以保证主元元素𝑥=𝐴[𝑟]是等概率地从子数组的𝑟−𝑝+1个元素中选取的。因为主元元素是随机选取的,我们期望在平均情况下,对输入数组的划分是比较均衡的。

练习

在这里插入图片描述

3-1

随机化算法不能改变最坏情况下得运行时间,但是能降低最坏情况发生的概率。

3-2
  • 最好情况是均匀划分,其时间复杂度 T(n)=2T(n/2)+1 =>T(n)=Θ(n)
  • 最坏情况是分成不平衡的划分,其时间复杂度 T(n)=T(n-1)+T(0)+1 各式相加得=>T(n)=Θ(n)

快速排序的期望运行时间

我们首先注意到每一对元素至多比较一次。因为各个元素只与主元元素进行比较,并且在某一次的Partition调用结束之后,该元素就再也不会与其他元素进行比较了。

我们的分析要用到指示器随机变量。定义

𝑋 𝑖 𝑗 = 𝐼 { 𝑧 𝑖 与 𝑧 𝑗 进 行 比 较 } = { 1 如 果 𝑧 𝑖 与 𝑧 𝑗 进 行 比 较 发 生 0 如 果 𝑧 𝑖 与 𝑧 𝑗 进 行 比 较 不 发 生 𝑋_{𝑖𝑗}=𝐼\{𝑧_𝑖与𝑧_𝑗进行比较\}=\begin{cases} 1 &\text如果𝑧𝑖与𝑧𝑗进行比较发生 \\ 0 &\text如果𝑧𝑖与𝑧𝑗进行比较不发生 \end{cases} Xij=I{zizj}={10zizjzizj

由于每一对元素至多比较一次,所以我们可以计算出算法的总比较次数:

X = ∑ i = 1 n − 1 ∑ j = i + 1 n X i j X=\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}X_{ij} X=i=1n1j=i+1nXij
对上式两边取期望
E ( X ) = E ( ∑ i = 1 n − 1 ∑ j = i + 1 n X i j ) = ∑ i = 1 n − 1 ∑ j = i + 1 n P r { 𝑧 𝑖 与 𝑧 𝑗 进 行 比 较 } E(X)=E(\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}X_{ij}) =\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}Pr\{𝑧_𝑖与𝑧_𝑗进行比较\} E(X)=E(i=1n1j=i+1nXij)=i=1n1j=i+1nPr{zizj}

  • 假设每一个元素是互异的。
  • 一旦一个满足𝑧𝑖<𝑥<𝑧𝑗的主元𝑥被选择后,我们就知道𝑧𝑖和𝑧𝑗以后再也不可能进行比较了
  • 另一种情况,如果𝑧𝑖在𝑍𝑖𝑗中的所有其他元素之前被选择为主元,那么𝑧𝑖就将与除了它自身以外的所有元素进行比较
  • 如果𝑧𝑗在𝑍𝑖𝑗中的所有其他元素之前被选择为主元,那么𝑧𝑗就将与除了它自身以外的所有元素进行比较。

𝑧𝑖与𝑧𝑗会进行比较,当且仅当𝑍𝑖𝑗中被选为主元的第一个元素是𝑧𝑖或者𝑧𝑗。

在这里插入图片描述
于是综上两式我们有:

E ( X ) = ∑ i = 1 n − 1 ∑ j = i + 1 n 2 j − i + 1 E(X) =\sum_{i=1}^{n-1}\sum_{j=i+1}^{n} \frac{2}{j-i+1} E(X)=i=1n1j=i+1nji+12
在求这个累加和时。可以将变量做个变换(𝑘=𝑗−𝑖),并利用有关调和级数的界,得到:

E ( X ) = ∑ i = 1 n − 1 ∑ j = i + 1 n 2 j − i + 1 = ∑ i = 1 n − 1 ∑ k = 1 n − i 2 k + 1 < ∑ i = 1 n − 1 ∑ k = 1 n 2 k = ∑ i n − 1 O ( lg ⁡ n ) = O ( n lg ⁡ n ) E(X) =\sum_{i=1}^{n-1}\sum_{j=i+1}^{n} \frac{2}{j-i+1}=\sum_{i=1}^{n-1}\sum_{k=1}^{n-i} \frac{2}{k+1} < \sum_{i=1}^{n-1}\sum_{k=1}^{n} \frac{2}{k} = \sum_i^{n-1}O(\lg n) = O(n \lg n) E(X)=i=1n1j=i+1nji+12=i=1n1k=1nik+12<i=1n1k=1nk2=in1O(lgn)=O(nlgn)

在输入元素互异的情况下,快速排序的期望运行时间为𝑂(𝑛𝑙𝑜𝑔𝑛)。

练习

在这里插入图片描述

4-1

在这里插入图片描述

4-2

最好情况就是均分的情况下,T(n)=2T(n/2)+Θ(n) 满足主定理case2=>T(n)=Θ(nlgn)=Ω(nlgn)

4-3

求抛物线方法(过程略)

4-4

在这里插入图片描述

4-5

证明过程略,k值如下
在这里插入图片描述

4-6

最坏概率 p(x<1-a∪x>a)=p(x>a)+p(x<1-a)=1-p(a<x<1-a)=1-(2a-1)=2-2a

思考题

7-1(Hoare划分的正确性) 本章中的PARTITION算法并不是其最初版本。下面给出的是最早由C.R.Hoare所设计的划分算法:

HOARE-PARTITION(A, p, r)
    x = A[p]
    i = p - 1
    j = r + 1
    while true
        repeat
            j = j - 1
        until A[j] ≤ x
        repeat
            i = i + 1
        until A[i] ≥ x
        if i < j
            exchange A[i] with A[j]
        else return j

a. 试说明 HOARE-PARTITION 在数组A={13,19,9,5,12,8,7,4,11,2,6,21}上的操作过程,并说明在每一次执行4-13行while循环时数组元素的值和辅助变量的值。

x=13, j = 9 and i=10

b.下标i和j可以使我们不会访问在子数组A[p…r]以外的数组A的元素

p≤i<j≤r

c.当HOARE-PARTITION结束时,它返回的值j 满足p<=j<r

d. 当HOARE-PARTITION结束时,A[p…r]中的每一个元素都小于或等于A[j+1…r]中的元素

e.利用HOARE-PARTITION,重写QUICKSORT算法

HOARE-QUICKSORT(A, p, r)
    if p < r
        q = HOARE-PARTITION(A, p, r)
        HOARE-QUICKSORT(A, p, q)
        HOARE-QUICKSORT(A, q + 1, r)

7-2 随机化快速排序的分析中,我们假设输入元素的值是互异的,在本题中,我们将看看如果这一假设不成立会出现什么情况?

a.如果所有输入元素的值都相同,那么随机化快速排序的运行时间会是多少?

如果所有输入元素的值都相同,那么每次划分都是极不平衡的。每次都是T(n)=T(0)+T(n-1)+Θ(n) T(n)=Θ(n^2)

b.PARTITION过程返回一个数组下标q,使得A[p…q-1]中的每个元素都小于等于A[q],而A[q+1…r]中的每个元素都大于A[q]。修改PARTITION代码来构造一个新的PARTITION‘(A,p,r),它排列A[p…r]的元素,返回值是两个数组下标q和t,其中p<=q<=t<=r.

  • A[q…t]中的所有元素都相等。
  • A[p…q-1]中的每个元素都小于A[q]。
  • A[t+1…r]]中的每个元素都大于A[q]。
PARTITION'(A, p, r)
    x = A[p]
    low = p
    high = p
    for j = p + 1 to r
        if A[j] < x
            y = A[j]
            A[j] = A[high + 1]
            A[high + 1] = A[low]
            A[low] = y
            low = low + 1
            high = high + 1
        else if A[j] == x
            exchange A[high + 1] with A[j]
            high = high + 1
    return (low, high)

c.将RANDOMIZED-QUICKSORT过程改为调用PARTITION’,并重新命名为RANDOMIZED-QUICKSORT‘。修改QUICKSORT的代码构造一个新的QUICKSORT’(A,p,r),它调用RANDOMIZED-QUICKSORT‘,并且只有分区内的元素互异时候才做递归调用。

QUICKSORT(A, p, r)
    if p < r
        (low, high) = RANDOMIZED-PARTITION(A, p, r)
        QUICKSORT(A, p, low - 1)
        QUICKSORT(A, high + 1, r)

d.在QUICKSORT‘中,应该如何改变7.4.2节中的分析方法,从而避免所有元素都是互异的这一假设?

(略)

7-3 (略)

7-4(快速排序的栈深度) 7.1节中的QUICKSORT算法包含了两个对其自身的递归调用。在调用PARTITION后,QUICKSORT分别递归调用了左边的子数组和右边的子数组。QUICKSORT中的第二个递归调用并不是必须的。我们可以用一个循环控制结构来代替它。这一技术成为尾递归,好的编译器都提供这以功能。考虑下面这个版本的快速排序,它模拟了尾递归情况。

TAIL-RECURSIVE-QUICKSORT(A, p, r)
    while p < r
        // Partition and sort left subarray.
        q = PARTITION(A, p, r)
        TAIL-RECURSIVE-QUICKSORT(A, p, q - 1)
        p = q + 1

a.证明:TAIL-RECURSIVE-QUICKSORT(A,1,A.length)能正确地对数组A进行排序

b.请描述一种场景,使得针对一个包含n个元素数组的TAIL-RECURISIVE-QUICKSORT的栈深度是Θ(n).

自身递归调用不超过n次。每次调用过程值需要O(1)的内存空间,所以不超过n次调用就是Θ(n)空间

c.修改TAIL_RECURSIVE_QICKSORT的代码,使其最坏情况下栈深度是Θ(lgn),并且能够保持O(nlgn)的期望时间复杂度。

MODIFIED-TAIL-RECURSIVE-QUICKSORT(A, p, r)
    while p < r
        q = PARTITION(A, p, r)
        if q < floor((p + r) / 2)
            MODIFIED-TAIL-RECURSIVE-QUICKSORT(A, p, q - 1)
            p = q + 1
        else
            MODIFIED-TAIL-RECURSIVE-QUICKSORT(A, q + 1, r)
            r = q - 1

主要参考

算法导论课后习题解析 第七章
算法导论第七章课后答案
Analysis of quicksort
算法导论 快速排序算法学习
算法导论第七章最后思考题

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值