该篇学习笔记来自于《你也能看得懂的python算法书》
分治算法的核心思想是将一个规模很大的问题化简为n个规模较小的问题,这些子问题虽然独立而不同,但问题的本质是一样的,从而达到分而治之的目的。
一、归并排列
归并算法是一种有效的排序方法,其他常见的排序算法包括冒泡排序、插入排序、二叉树排序、快速排序、堆排序等。归并排序是分治的一个非常经典的应用。归并算法有两种类型:迭代法和递归法,两种方法的代码有一大部分是重复的,这是因为两者都使用指针排序合并两个子列表,只不过一个应用迭代,一个应用递归。
1. 迭代法
运用for循环把这些子列表成对排序合并,最后组成一个列表整体。
def mergeSort(mylist):
length=len(mylist)
n=1#n为子数组长度,初始值为1
while n<length:
for i in range(0,length,2*n):
listA=mylist[i:i+n]
listB=mylist[i+n:i+2*n]
pointA=0#利用指针将listA与listB排序
pointB=0
counter=i
while pointA<len(listA) and pointB<len(listB):
if listA[pointA]<listB[pointB]:
mylist[counter]=listA[pointA]
pointA+=1
counter+=1
else:
mylist[counter]=listB[pointB]
pointB+=1
counter+=1
while pointA<len(listA):
mylist[counter]=listA[pointA]
pointA+=1
counter+=1
while pointB<len(listB):
mylist[counter]=listB[pointB]
pointB+=1
counter+=1
n=n*2#将n乘2
list=[3,6,7,2,8,9,4,10]
mergeSort(list)
print(list)
2.递归法
递归法的核心思想是把列表分解成两个子列表,单独排序子列表再合并子列表,把列表的问题递给子列表,一环套一环的解决问题。
def mergeSort(mylist):
length=len(mylist)
if length<2:#如果mylist只有一个元素,终止
return
cut=length//2
listA=mylist[:cut]
listB=mylist[cut:]
mergeSort(listA)#把左子列表归并排序
mergeSort(listB)#把右子列表归并排序
pointA=0#利用指针将listA与listB排序
pointB=0
counter=0
while (pointA<len(listA) and pointB<len(listB)):
if listA[pointA]<listB[pointB]:
mylist[counter]=listA[pointA]
pointA+=1#移动指针
counter+=1
else:
mylist[counter]=listB[pointB]
pointB+=1
counter+=1
while pointA<len(listA):#B指针走到尽头而A没有
mylist[counter]=listA[pointA]#指针A对应的元素替换mylist[counter]
pointA+=1
counter+=1
while pointB<len(listB):#A指针走到尽头而B没有
mylist[counter]=listB[pointB]
pointB+=1
counter+=1
list=[3,6,7,2,8,9,4,10]
mergeSort(list)
print(list)
二、连续子列表的最大和
最大子列表有可能在左子列表、右子列表或左子列表与右子列表之间,我们需要找的是左子列表的最大子列表和、右子列表的最大子列表和、左子列表与右子列表之间的子列表的最大和,再进行比较。
如何找到左子列表与右子列表的最大子列表的和呢?
分治的想法是:让左子列表与右子列表的子列表回答这个问题就行了,此时不需要知道答案,直到分治到最后列表的长度变为1
如何找到左子列表与右子列表之间的子列表的最大和?
设一个中点,遍历中点左边,跟踪记录已遍历过的值的总和,取总和的最大值;遍历中点右边,跟踪记录已遍历过的值的总和,取总和的最大值。最后,中点值加上左边的最大值再加上右边的最大值就是想要的值。
def maxArray(Array):
if Array==[]:#列表为空则终止
return
if len(Array)==1:
return Array[0]
n=len(Array)//2
maxleftsum=maxArray(Array[:n])#分治:找到左子列表的最大和
maxrightsum=maxArray(Array[n:])#找到右子列表的最大和
maxleft=0
maxright=0
#找到左子列表与右子列表之间的子列表的最大和
for i in range(n-1,-1,-1):#遍历中点左边的元素
maxleft=max(maxleft,maxleft+Array[i])
for j in range(n+1,len(Array),1):#遍历中点右边的元素
maxright=max(maxright,maxright+Array[j])
return max(maxleft+maxright+Array[n],maxleftsum,maxrightsum)#返回三个可能的最大值
list=[3,5,-3,6,4,9,-12,10,14]
maxsum=maxArray(list)
print(maxsum)
三、几何问题之凸包
凸包是正好包含所有点的凸多边形,可以将它想象成一个包围点集合的橡皮筋。之所以叫凸包正是这个“凸”多边形“包”围所有点。该几何问题是怎样在点集合中找出凸包的顶点集,这一问题有不同的计算方式,常见的有Graham扫描法,Jarvis步进法,快包法,分治法,以下为分治法。
思路:
首先分出上包与下包,然后找出两个最远的点连线,然后再次分出两个上包与下包。在此之后,上包递归分出两个子上包,下包递归分出两个字下包,直至包中节点数小于等于1,每次递归都把最远点加入顶点集。每次递归只找一个顶点,并把其余顶点交给子问题,也就是子包。
from math import sqrt
solution=[]#全局变量
def convexHull(points):
if len(points)<=3:
return points
global solution
left_most=points[0]#最左点
right_most=points[len(points)-1]#最右点
solution.attend(left_solution,right_solution)#加入顶点集合
helper(points,left_solution,right_solution,True)
helper(points,left_solution,right_solution,False)
return solution
#helper方法,参数为:点集合,最左点,最右点,布尔值
def helper(points,left_solution,right_solution,upBool):
global solution
if len(points)<=1:#第一次调用,点集合不包括最左点和最右点
return
l=Helperline(left_most,right_most)
if upBool:
up=[]
max_point=()
max_distance=0
for point in points:
distance=0-(1[0]*point[0]+l[1]*point[1]+l[2])/sqrt(l[0]*l[0]+l[1]*l[1])#与直线的距离
if distance>0:
up.append(point)
if distance>max_distance:
max_distance=distance
max_point=point
if max.point()!=0:
solution.append(max_point)#将最远点加入顶集
helper(up,left_most,max_point,True)#递归左上包
helper(up,max_point,right_most,True)#递归右上包
else:
down=[]
min_point=()
min_distance=0
for point in points:
distance=0-(1[0]*point[0]+l[1]*point[1]+l[2])/sqrt(l[0]*l[0]+l[1]*l[1])
if distance>0:
down.append(point)
if distance>min_distance:
min_distance=distance
min_point=point
if min.point()!=0:
solution.append(min_point)#将最远点加入顶集
helper(down,left_most,min_point,False)#递归左下包
helper(down,min_point,right_most,False)#递归右下包
def Helperline(point,point2):#传入两点,计算直线Ax+By+C=0,传入[A,B,C]
if (point[0]-point2[0]!=0):
n=(point[1]-point2[1])/(point[0]-point2[0])
m=point2[1]-point[0]*n
return(n,-1,m)
else:
return(1,0,point[0])
if __name__ == '__main__':
inp = [(0,0), (0,4), (-4, 0), (5, 0), (0, -6), (1, 0)]
# out = [(-4, 0), (5, 0), (0, -6), (0, 4)]
print(convex_Hull(inp))
def helper(points,left_solution,right_solution,upBool):中为什么要加入参数upBool?
为了不构建两个helper方法,但仍区别对待上包与下包,加入参数upBoo,upBool为True代表上包,upBool为False代表下包。
helper方法完成的过程包括什么?
1.利用Helperline求出计算点left_most与right_most的直线公式
2.计算点集合中每点与直线的距离
3.如果为上包,把距离大于零的加入新集合,并找出最远点
4.如果为下包,把距离小于零的加入新集合,并找出最远点
5.再次调用helper,传入新参数集合与最远点
已知直线Ax+By+C=0的[A,B,C]参数与一点point的(x,y)怎么得到此点与该直线的距离?
将点传入距离公式即可:distance=1[0]*point[0]+l[1]*point[1]+l[2])/sqrt(l[0]*l[0]+l[1]*l[1]
四、数学问题之多项式的乘法
若把多项式展开相乘,时间复杂度是n*n
,但通过FFT(快速傅里叶变换)可降低时间的复杂度,变为n*
log
2
n
\log_2 n
log2n
from cmath import pi
from cmath import exp
def FFT(A,w):
length=len(A)
if length==1:
return [A[0]]#只有常数项,则直接返回常数项系数
else:
A_even=[]
A_odd=[]
for i in range (0,length//2):
A_even.append(A[2*i])
A_odd.append(A[2*i+1])
F_even=FFT(A_even,w**2)
F_odd=FFT(A_odd,w**2)
x=1
values=[None] * length#初始化列表,A的点值表示
for i in range(0,length//2):
values[i]=F_even[i]+x*F_odd[i]
values[i+length//2]=F_even[i]-x*F_odd[i]
x*=w
return values
def solver(A,B):
length=len(A)+len(B)-1#length为大于len(A)+len(B)-1的最小的2的幂次方
n=1
while 2**n<length:
n+=1
length=2**n
A.extend([0]*(length-len(A)))#在列表后面补上[0]扩展到length,1j相当于复数虚部i
B.extend([0]*(length-len(B)))
w=exp(2*pi*1j/length)#length次单位负数根
A_values=FFT(A,w)
B_values=FFT(B,w)
C_values=[A_values[i]*B_values[i] for i in range(length)]
#将c的点值转换为系数表述,FFT传入w的倒数,实现插值
result=[int((x/length).real) for x in FFT(C_values,w**-1)]
while result[-1]==0:
del result[-1]
return result
if __name__ == '__main__':
print(solver([3,2,3,4],[2,0,1]))
- 为什么要将length的长度填充为2的幂次方?
FFT的最小计算单元为2,确定length只能为2的倍数,而且不可能是像6或10这样的数字,因为FFT还是对折运算,即不断从中间对折直到算到最小单元为止,由此可知,其数据量只能是2的幂次。如果你的数据只有6个,那么它运算起来至少是将这些数据当8个来运算的。 - 快速求值时要怎样特殊取值?
w=exp(2*pi*1j/length)#length次单位负数根,1j相当于复数虚部i
x=1 x*=w
阅读过程中若有疑问,可点此链接执行代码结果:FFT
因为使用的是codingground这一款在线编译器,输入时不支持中文字符,在此对不同python编译器的解码性能做一解释,python2默认使用ASCII
编码格式,而ASCII
编码中不包括中文字符,只有Unicode
或者UTF-8
编码才支持中文等字符;但python3使用的是UTF-8
编码格式