笔记:《算法图解》第四章:D&C算法、快速排序

1.分而治之的算法(Devide and Conquer)——将问题逐步分解

D&C算法是递归的,使用D&C解决问题的过程包括两个步骤:
(1) 找出基线条件,这种条件必须尽可能简单。
(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。
提示:编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。陷入困境时,请检查基线条件是不是这样的。

D&C算法示例:习题4.1,习题4.2,习题4.3

2.快速排序

快速排序是最快的排序算法之一,也是D&C典范。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下
(1)首先设定一个分界值(基准值pivot),通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

快速排序代码:

def quick_sort(data):    
    """快速排序"""    
    if len(data) >= 2:  # 递归入口及出口        
        mid = data[len(data)//2]  # 选取基准值,也可以选取第一个或最后一个元素        
        left, right = [], []  # 定义基准值左右两侧的列表        
        data.remove(mid)  # 从原始数组中移除基准值        
        for num in data:            
            if num >= mid:                
                right.append(num)            
            else:                
                left.append(num)        
        return quick_sort(left) + [mid] + quick_sort(right)    
    else:        
        return data
 
# 示例:
array = [2,3,5,7,1,4,6,15,5,2,7,9,10,15,9,17,12]
print(quick_sort(array))
# 输出为[1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 9, 9, 10, 12, 15, 15, 17]

性能分析:
快速排序的一次划分算法从两头交替搜索,直到low和high重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。
理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。
最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n2)。
为改善最坏情况下的时间性能,可采用其他方法选取中间数。通常采用“三者值取中”方法,即比较H->r[low].key、H->r[high].key与H->r[(10w+high)/2].key,取三者中关键字为中值的元素为中间数。
可以证明,快速排序的平均时间复杂度也是O(nlog2n)。因此,该排序方法被认为是目前最好的一种内部排序方法。

https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/369842?fromtitle=%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F&fromid=2084344&fr=aladdin

练习

4.1 请编写前述sum函数的代码。
4.2 编写一个递归函数来计算列表包含的元素数。
4.3 找出列表中最大的数字。
4.4 还记得第1章介绍的二分查找吗?它也是一种分而治之算法。你能找出二分查找算法的基线条件和递归条件吗?

4.5 ~4.8 使用大O表示法时,下面各种操作都需要多长时间?
4.5 打印数组中每个元素的值。
4.6 将数组中每个元素的值都乘以2。
4.7 只将数组中第一个元素的值乘以2。
4.8 根据数组包含的元素创建一个乘法表,即如果数组为[2, 3, 7, 8, 10],首先将每个元素都乘以2,再将每个元素都乘以3,然后将每个元素都乘以7,以此类推。

答案

4.1 编写一个递归函数来计算列表元素的和。

def sum(list):
	if list == []:
		return 0
	return list[0]+sum(list[1:])

4.2 编写一个递归函数来计算列表包含的元素数。

def count(list):
  if list == []:
    return 0
  return 1 + count(list[1:])

4.3 找出列表中最大的数字。

def max_(lst):
  if len(lst) == 0:
    return None
  if len(lst) == 1:
    return lst[0]
  else:
    sub_max = max_(lst[1:])
    return lst[0] if lst[0] > sub_max else sub_max

4.4 二分查找的基线条件是数组只包含一个元素。如果要查找的值与这个元素相同,就找到了!否则,就说明它不在数组中。在二分查找的递归条件中,你把数组分成两半,将其中一半丢弃,并对另一半执行二分查找。
4.5 O(n)。
4.6 O(n)。
4.7 O(1)。
4.8 O(n^2)。

《算法图解》- Aditya Bhargava

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值