分治算法python

该篇学习笔记来自于《你也能看得懂的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]))
  1. 为什么要将length的长度填充为2的幂次方?
    FFT的最小计算单元为2,确定length只能为2的倍数,而且不可能是像6或10这样的数字,因为FFT还是对折运算,即不断从中间对折直到算到最小单元为止,由此可知,其数据量只能是2的幂次。如果你的数据只有6个,那么它运算起来至少是将这些数据当8个来运算的。
  2. 快速求值时要怎样特殊取值?
    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编码格式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值