本章内容:
目录
分而治之
快速排序
再谈大O表示法
分而治之并非可用于解决问题的算法,而是一种解决思路。
使用分而治之策略解决问题的过程包括两个步骤:
实例一:
比如编写求解一个数字数组之和的函数
第一步,找到基线条件。最简单的数组应该是不包含元素或者是只包含一个元素,这个就是基线条件。
第二步,缩小规模。如何将一个数组缩小规模,可以每一次都从数组中提取一个数字,然后将这个数字和剩下的数组进行求和,这样数组的规模便可以缩小。采用递归的手段,因此每一次调用,都会离基线条件更近一步。
def sum(arr):
if arr == []:
#基线条件
return 0
else:
number = arr.pop()
newarr = arr
return number+sum(newarr)
print(sum([1,2,3,4]))
提示:编写设计数组的递归函数时候,基线条件通常是数组为空或者只包含一个元素。陷入困境时,请检查基线条件是不是这玩样的。
实例二:
比如有一块土地,你要将这块地均匀分成方块,且分出来的方块尽可能的大
第一步,找到基线条件。最容易的处理情况是,一边的长度是第二条边的整数倍
第二步,缩小规模。比如现在是一大块方地(1680*640)那么可以将其划分为两个(640*640)的正方形以及一个(640*400)的矩形,那么那块(640*400)的便是缩小规模后的土地,依次类推。
(欧几里得算法:适用这小块地的最大方块,也是适用于整块地的最大方块。哈哈哈记住这个便可以了。对于这一个过程最好还是画一下图,对每一步的过程都画一画)
def find_min_squre(a,b):
#假设现在那块地其中一边长a,另外一边长b
x = max(a,b)
y = min(a,b)
#找出两条边中较长边和短边
if x == 2*y:
return y
#要是符合基线条件那么直接输出最小边,即为可切最大正方形的边长
else:
return find_min_squre(x%y,y)
#这个x%y的意思是求出原来的长方形切去若干个正方形后剩下的长度
print(find_min_squre(1680,640))
输出结果是80
快速排序是一种常用的排序算法,比选择排序快很多。例如,c语言中标准库中的函数qsort实现的就是快速排序。快速排序也是使用的是分而治之的方法。下面使用快速排序对数组进行排序。
基线条件:
数组为空或者是只包含一个元素。在这种情况下,只需返回原数组——根本不需要排序。
缩小规模:
需要将数组进行分解,直至达到基线条件。要是有多个元素,首先从数组中选择一个元素,这个元素称之为基准值(pivot)。接下来,找出比这个基准值小的元素以及比这个基准值大的元素,这个被称为分区(pratitioning)。
此时则会有:
这个里面的子数组是无序的。
若是这两个字数组都是有序的,那么是不是
原来的数组排序 = 其中一个有序的子数组 + 基准值 + 另外一个有序的子数组
其实,很容易就可以想到了,这两个无序的子数组变成有序的子数组也是采用相同的方法,即取出基准值然后分成两个子数组,分而治之。比如找个例子,有个数组 [4,5,2,7,1,3] 要采用这个方法进行排序。
这里用从大到小的方式排列,也就是把大的数组放在左边
def quicksort(arr):
if len(arr) < 2:
return arr
else:
pivot = arr[0]
smaller = [i for i in arr[1:] if i <= pivot]
bigger = [i for i in arr[1:] if i > pivot]
return quicksort(smaller) + [pivot] + quicksort(bigger)
print(quicksort([4,5,2,7,1,3]))
输出结果为 [1, 2, 3, 4, 5, 7]
快速排序的独特之处在于选择的基准值,快速排序的平均时间为 O(n*log n)。一般情况下,直接随机取基准值就好。
常见的大O运算时间 | |||||
---|---|---|---|---|---|
算法 | 二分查找 | 简单查找 | 快速排序 | 选择排序 | 旅行商问题算法 |
快 --> 慢 | O(log n) | O(n) | O(n*log n) | O(log n*n) | O(log n!) |