算法图解第四章笔记与习题(快速排序)

算法图解第四章笔记与习题(快速排序)



4.1 分而治之

分而治之(divide and conquer)D&G是一种著名的递归式问题解决方法。——分而治之是递归的。

分而治之的算法是递归的。使用分而治之算法解决问题的过程包括两个步骤。

(1)找出基线条件,这种条件必须尽可能接单。

(2)不断地将问题分解(或者说缩小规模),直到使其符合基线条件。

例如,对于数组类问题的基线条件,则常常是数组为空,或数组内仅有一个元素。分解方式则通常是将其分为两个尽可能等大小的数组。


4.2 快速排序

def quicksort(array):
    if len(array) < 2:    # 基线条件:为空或只包含一个元素的数组是“有序”的
        return array
    else:                 # 递归条件
        pivot = array[0]                                # 选定数组中的第一个值为基准值
        less = [i for i in array[1:] if i <= pivot]     # 得到比基准值小的数字组成的数组
        greater = [i for i in array[1:] if i > pivot]   # 得到比基准值大的数字组成的数组
        return quicksort(less) + [pivot] + quicksort(greater)     # 将子数组进行快速排序,再与基准值一同组成有序数组。
    
print(quicksort([10, 5, 2, 3]))


4.3 大 O O 表示法的平均情况和最糟情况

  • 最糟情况指的是算法运行时可能遇到的最坏的情况。

  • 平均情况指的是算法遇到时的最佳情况。(最佳情况也是平均情况)

快速排序的平均情况为 O ( n log n ) O(n\log n) ,最糟情况为 O ( n 2 ) O(n^2) 。这取决于基准值选取的好坏。

当每次基准值选取的均为最差情况时,则需要对包含 n n 个元素的数组进行 n n 次基准值的选取。当每次基准这选取的均为最佳情况时,则仅需要 log n \log n 次选取。(二分)

而当每一次选取基准值后,数组的每个元素均需要和基准值进行对比,因此,每次选取基准值之后需要进行 n n 次比较。

因此,快速排序的最糟情况为 O ( n n ) = O ( n 2 ) O(n* n)=O(n^2) ,平均情况为 O ( n log n ) O(n\log n)


4.4 小结

  • 分而治之算法将问题逐步分解。使用分而治之算法处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
  • 实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为 O ( n log n ) O(n \log n)
  • O O 表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
  • 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时, O ( log n ) O(\log n) 的速度比 O ( n ) O(n) 快得多。

练习

习题4.1

  • 请编写前述sum函数的代码。(递归求和,基线条件为数组空)
def sum(arr):
    if arr  == []:    # 基线条件:数组为空
        return 0
#    elif len(arr) == 1:    # 基线条件:数组内只有一个元素(可省略)
#        return arr[0]
    else:
        return arr[0] + sum(arr[1:])    # 递归条件:计算数组中第一个元素外元素的和,并与第一个元素再相加

习题4.2

  • 编写一个递归函数来计算列表包含的元素数。
def count(arr):
    if arr  == []:    # 基线条件:数组为空
        return 0
#    elif len(arr) == 1:    # 基线条件:数组内只有一个元素(可省略)
#        return 1
    else:
        return 1 + count(arr[1:])    # 递归条件:计算数组中第一个元素外剩余元素个数,再加一

上面两题中,数组内只有一个元素的情况均可省略。因为数组只有一个元素——即剩余数组为空的情况,已经被基线条件包含了,因此可以省略。


习题4.3

  • 找出列表中最大的数字。
def max(arr):
    if arr  == []:    # 基线条件:数组为空
        return None
    elif len(arr) == 1:    # 基线条件:数组内只有一个元素
        return arr[0]
    elif len(arr) == 2:    # 基线条件:数组内有两个元素,返回最大值(可用内置的max()代替)
        if arr[0] > arr[1]:
            return arr[0]
        else:
            return arr[1]
    else:
        return max([arr[0], max(arr[1:])])    # 递归条件:比较第一个元素与数组中剩余元素的最大值。

习题4.4

  • 还记得第1章介绍的二分查找吗?它也是一种分而治之算法。你能 找出二分查找算法的基线条件和递归条件吗?

基线条件:数组中仅剩一个元素(是否为查找元素为另外的判断,不属于基线条件之内)

递归条件:将数组分为两部分,如果查找值比第一部分的最大值小,则对第一部分继续二分查找,否则对剩余部分进行二分查找。


使用大O表示法时,下面各种操作都需要多长时间?

习题4.5

  • 打印数组中每个元素的值。

每个元素进行一次打印操作。$O(n) $。


习题4.6

  • 将数组中每个元素的值都乘以2。

n个元素进行一次乘法操作。故为 O ( n ) O(n)


习题4.7

  • 只将数组中第一个元素的值乘以2。

O ( 1 ) O(1)


习题4.8

  • 根据数组包含的元素创建一个乘法表,即如果数组为[2, 3, 7, 8, 10],首先将每个元素 都乘以2,再将每个元素都乘以3,然后将每个元素都乘以7,以此类推。

n个元素做n次乘法操作。故为 O ( n 2 ) O(n^2)


猜你喜欢

转载自blog.csdn.net/hwl19951007/article/details/88555445