Chapter7:快速排序

本文深入探讨了快速排序算法的实现细节及性能分析,包括快速排序的基本流程、不同场景下的时间复杂度评估、随机化版本的改进及其期望运行时间,并讨论了算法在实际应用中的表现。

快速排序

def partition(A, p, r):
    j = p - 1
    for i in range(p, r + 1):
        if A[i] <= A[r]:
            j += 1
            A[j], A[i] = A[i], A[j]
    return j

def quickSort(A, p, r):
    if p < r:
        q = partition(A, p, r)
        quickSort(A, p, q - 1)
        quickSort(A, q + 1, r)
7.1-2

A[p...r]中元素都相同时,返回q=r

def partition(A, p, r):
    j = p - 1
    same = True
    for i in range(p, r + 1):
        if A[i] != A[r]:
            same = False
        if A[i] <= A[r]:
            j += 1
            A[j], A[i] = A[i], A[j]
    return (p + r) / 2 if same else j
7.1-3

partition中只有一层循环,循环内部操作时间复杂度为O(1),循环次数为子数组规模n,循环外操作时间复杂度为Θ(1),综上partition的时间复杂度为Θ(n)

7.1-4
def partition(A, p, r):
    j = p - 1
    for i in range(p, r + 1):
        if A[i] >= A[r]:
            j += 1
            A[j], A[i] = A[i], A[j]
    return j

def qsort(A, p, r):
    print A, p, r
    if p < r:
        q = partition(A, p, r)
        qsort(A, p, q - 1)
        qsort(A, q + 1, r)

快速排序性能分析

依赖于划分是否均衡,均衡—>归并排序,不均衡—>插入排序

最坏情况:T(n)=T(n1)+Θ(n),时间复杂度Θ(n2)

与插入排序不同,当输入有序时,插入排序的时间复杂度为Θ(n),而快速排序的时间复杂度为Θ(n2)

最好情况:均衡划分,

T(n)=2T(n2)+Θ(n)
7.2-1

T(n)=T(n1)+Θ(n)=k=1nΘ(k)=Θ(k=1nk)=Θ(n2)
7.2-2

当所有的元素都相等时,即最坏情况,时间复杂度为Θ(n2)

7.2-3

对规模为n的输入,partition的时间复杂度为固定的Θ(n),输入降序排列,分割后生成两个大小为0和n-1的子问题,即出现最坏情况,如7.2-1所示,时间复杂度为Θ(n2)

7.2-5

nk记第k层占比α一支问题规模

n0=n

nk=nk1α=αkn

最后一层的问题规模nk=1

k=lgnlgα

同理可求最大深度约为

k=lgnlg(1α)
7.2-6

输入数组是完全随机

可以认为partition给出的划分结果q的在1...n上分布是等可能的,即

P(q=i)=1n, i=1n

原问题就变成了一个古典概型问题

P(产生比1α:α更平衡的划分) =

#(nα<i<n(1α))n=12α

快速排序的随机化版本

import random

def randomPartition(A, p, r):
    i = random.randint(p, r)
    A[i], A[r] = A[r], A[i]
    partition(A, p, r)

def qsort(A, p, r):
    if p < r:
        q = randomPartition(A, p, r)
        qsort(A, p, q - 1)
        qsort(A, q + 1, r)
7.3-1

如果输入是完全随机的,那么最坏情况出现的概率应该是很小的,对算法整体效率的参考价值不大,我们更加关注综合考虑的各种输入情况下,算法的平均效率,也就是期望运行时间。

7.3-2

不论好坏,总要调用Θ(n)

递归树中在每个非叶节点处调用一次随机数生成器,叶节点一共n个

m2=n

m=2n1 or 2n

非叶子节点数目为

mn=n1 or n

当输入数据已经基本有序时,插入排序的速度很快,接近线性时间复杂度

7.4-5

因为当子数组的长度小于k时结束递归,那么递归树的深度为

O(lgnk)
递归树每层分割操作需要
Θ(n)

所以快排阶段时间复杂度为

O(nlgnk)

插入排序阶段,每个元素比较的次数为

O(k)
所以插入排序阶段的时间复杂度为
O(nk)

综上,整体的时间复杂度为

O(nk+nlgnk)

如何选择合适的k,以优化时间复杂度???

7.4-6

在题意表述的情况下,最坏划分是生成大小为1和n-2的子问题

z1, z2,  zn记数组的顺序排序结果

P{α:1α}C1αnC1(1α)nC3n=6α(1α)n(n1)(n2)

思考题

7-1
def hoarePartition(A, p, r):
    x = A[p]
    i = p - 1
    j = r + 1
    while True:
        j -= 1
        while A[j] > x:
            j -= 1
        i += 1
        while A[i] < x:
            i += 1
        if i < j:
            A[i], A[j] = A[j], A[i]
        else:
            return j
7-1.b

循环不变量:

每次迭代前一定存在k[p,j1], A[k]x,一定存在l[i+1,r], A[l]x

初始化:

第一次迭代前,k=p, l=p,满足循环不变量

保持:

因为第i次迭代前,一定存在k[p,j1], A[k]x一定存在l[i+1,r], A[l]x

所以第i次迭代时,7、8行循环和10、11行循环一定能在合法的索引值处结束循环,合法索引值即为j=k, i=l

如果i<j,交换A[i],A[j]后,存在k=i[p,j1], A[k]x存在l=j[i+1,r], A[l]x,循环不变量得以保持

结束:

ij,结束循环,至此为止从未发生越界访问,所以算法不会访问A[p...r]以外的数据

7-1.c

只要证jr,再结合7-1.b,即证得pj<r

反证,如果返回值j=r,则ij=r

又由7-1.b知pir

所以只能有i=r

因为j=r意味着外层循环只执行了一次就返回结果

又已知在外层循环第一次执行时,第11行循环根本不会执行,所以i=p

i=p=r,意味着输入数组规模为1,与大前提数组至少包含两个元素相矛盾

所以jr

7-1.d

循环不变量:

每次迭代后A[p...i]中的每一个元素都小于等于A[j...r]中的元素

保持:

6、7、8行结束后,确保A[j]xA[j+1r]中的元素都大于x

9、10、11行结束后,确保A[i]xA[p...i1]中的元素都小于x

i<j,交换A[i], A[j],A[p…i]中的每一个元素都小于等于x,A[j…r]中的元素都大于等于x,循环不变量成立

结束:

6、7、8行结束后,确保A[j]xA[j+1r]中的元素都大于x

9、10、11行结束后,确保A[i]xA[p...i1]中的元素都小于x

ij,只能是i=ji=j+1,无论何种情况都有A[p...j]中的每一个元素都小于等于A[j+1...r]中的元素

7-1.e
def qsort(A, p, r):
    if p < r:
        q = hoarePartiton(A, p, r)
        qsort(A, p, q)
        qsort(A, q + 1, r)
7-2
7-2.a

当所有元素都相同,每次分割时间为Θ(n)

分割后生成大小为0和n-1的子问题,所以时间复杂度T(n)=T(n1)+Θ(n)

解得T(n)=Θ(n2)

7-2.b
import random

def modifiedPartition(A, p, r):
    x = A[r]
    q = p - 1
    for i in range(p, r + 1):
        if A[i] < x:
            q += 1
            A[i], A[q] = A[q], A[i]
    t = r + 1
    for i in reversed(range(p, r + 1)):
        if A[i] > x:
            t -= 1
            A[i], A[t] = A[t], A[i]
    return q, t

def randomPartition(A, p, r):
    x = random.randint(p,r)
    A[x], A[r] = A[r], A[x]
    modifiedPartition(A, p, r)

def qsort(A, p, r):
    if p < r:
        q, t =  modifiedPartition(A, p, r)
        qsort(A, p, q)
        qsort(A, t, r)
7-3
7-3.a

古典概型,

E[Xi]=1n
7-3.b&c

T(n)=q=1nXq(T(q1)+T(nq)+Θ(n))

E[T(n)]=E[q=1nXq(T(q1)+T(nq)+Θ(n))]

E[T(n)]=q=1nE[Xq(T(q1)+T(nq))]+Θ(n)

XqT独立
E[T(n)]=2nq=0n1E[T(q)]+Θ(n)

E[T(n)]=2nq=2n1E[T(q)]+Θ(n)

7-4

7-4.a

循环不变量:

每次迭代前A[1,p1]中的元素有序排列,且都小于等于A[p,A.length]中的元素

初始化:

第一次迭代前,A[1,0]是空的子数组,满足循环不变量

保持:

第i次迭代前A[1,p1]中的元素有序排列,且都小于等于A[p,A.length]中的元素

A[p,r]进行分割后,A[p,q1]中的元素都小于等于A[q+1,r],对A[p,q1]排序后,其内元素有序

此时A[1,q1]中的元素有序排列,且都小于等于A[q+1,A.length]

p=q,循环不变量保持

结束:

p>=A.length
A[p,A.length]或为空的子数组或者只有一个元素,A[1,p1]中的元素有序排列,且都小于等于A[p,A.length]中的元素,所以A被正确排列

7-4.b

每次递归调用的规模比当前规模小1

7-4.c

每次迭代挑选规模较小的子问题来递归处理,而规模较大的子问题使用迭代处理

def tailRecursiveQuickSort(A, p, r):
    while p < r:
        q = randomPartition(A, p, r):
        if (q - p) < (r - q):
            tailRecursiveQuickSort(A, p, q - 1)
            p = q + 1
        else:
            tailRecursiveQuickSort(A, q + 1, r)
            r = q - 1

栈深度d=O(lgn)

最坏栈深度,每次都对半分,d=Θ(lgn)

7-5

7-5.a

Pi=C1i1C1niC3n=6(i1)(ni)n(n1)(n2)
7-5.b

Pmid=6(n+121)(nn+12)n(n1)(n2)

平凡实现

Pmid=1n

limn>Pmid=0
7-5.c

f(x)=3(x1)2x(x2)

Pgood=2s/3s/3f(t)dt
clear All;
x = 3:1:100;
y = zeros(1,98);
z = ones(1,98) ./ 3;

syms t
for i = 1:1:98
    y(i) = int(6*(t-1)*(x(i)-t)/(x(i)*(x(i)-1)*(x(i)-2)), x(i)/3, 2*x(i)/3);
end

plot(x,y,'b*',x,z,'g+');

两种实现的差距

这里写图片描述

7-6

思路:

参考7-2的思想

选定主元,根据是否与主元的位置关系进行分割

与主元不重叠,位于主元左侧则向左集中,与主元不重叠,位于主元右侧则向右集中

如果与主元重叠,则更新主元范围为重叠部分,重叠部分不需要排序

def intersect(x, y):
    if y[0] <= x[0] and y[1] < x[1]:
        return [x[0], y[1]]
    elif y[0] > x[0] and y[1] < x[1]:
        return list(y)
    elif y[0] < x[0] and y[1] >= x[1]:
        return [y[0],x[1]]
    else:
        return x

def partition(A, p, r):
    x = list(A[r])
    q = p - 1
    t = r + 1
    i = p
    for n in range(p, r + 1):
        if A[i][1] <= x[0]:
            q += 1
            A[i], A[q] = A[q], A[i]
            i += 1
        elif A[i][0] >= x[1]:
            t -= 1
            A[i], A[t] = A[t],A[i]
        else:
            x = intersect(x, A[i])
            i += 1

def fuzzySort(A, p, r):
    if p < r:
        q, t = partition(A, p, r)
        fuzzySort(A, p, q)
        fuzzySort(A, t, r)

bug:分割后问题规模可能不会减小

import random

def intersect(x, y):
    if y[0] <= x[0] and y[1] < x[1]:
        return [x[0], y[1]]
    elif y[0] > x[0] and y[1] < x[1]:
        return list(y)
    elif y[0] < x[0] and y[1] >= x[1]:
        return [y[0],x[1]]
    else:
        return x

def partition(A, p, r):
    x = list(A[r])
    q = p - 1
    t = r
    i = p
    for n in range(p, r):
        if A[i][1] <= x[0]:
            q += 1
            A[i], A[q] = A[q], A[i]
            i += 1
        elif A[i][0] >= x[1]:
            t -= 1
            A[i], A[t] = A[t],A[i]
        else:
            x = intersect(x, A[i])
            i += 1
    A[r], A[t] = A[t], A[r]
    return q, t + 1

def randomPartition(A, p, r):
    i = random.randint(p,r)
    A[r], A[i] = A[i], A[r]
    return partition(A, p, r)

def fuzzySort(A, p, r):
    if p < r:
        q, t = randomPartition(A, p, r)
        fuzzySort(A, p, q)
        fuzzySort(A, t, r)

当所有元素都重叠时,调用randomPartition后生成的两个子问题的规模都是0

randomPartition的时间复杂度为Θ(n),所以这个时候算法的时间复杂度为Θ(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值