分治算法

本文为自己的总结记录,参考博文分治算法总结很多。

算法思想及步骤

如字面意思,是“分而治之”。通常会用到递归。
对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,为同类为题,就递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

分治法在每一层递归上都有三个步骤
step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
step3 合并:将各个子问题的解合并为原问题的解。

思维过程:
类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法 ,利用数学建立可解模型
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。

时间复杂度分析

在分治算法中的三个步骤中, 我们假设分解和合并过程所用的时间分别为D(n),C(n), 设T(n)为处理一个规模为n的序列所消耗的时间,b为子序列个数,每一个子序列是原序列的1/b,α为把每个问题分解成α个子问题,则所消耗的时间为
如果n<=c(c是n中一个可以直接求解的规模。),T(n)= f(1)
否则 T(n)= αT(n/b)+ D(n) + C(n)
在快速排序中,α 为2, b也为2, 分解(就是取参照点,可以认为是1),合并(把数组合并,为n), 因此D(n) + C(n) 是一个线性时间f(n).
这样时间就变成了T(n) = 2T(n/2) +f(n).

每一次的时间复杂度为:
第一次是cn(c为比较一次时所用的时间,n为分成的子序列个数),
第二次时数组被分成了两部分, 每部分为n/2, 所以第二次时间为 c * n/2 + c
n/2 =cn,
第三次被分成了四部分, 时间为cn/4 +cn/4 + cn/4 + cn/4 =cn,
层高一共是按刚才说的是Log2n层,每一层上都是cn, 所以共消耗时间cn * Log2n;
**则总时间:
c
n * Log2n +cn = cn(1+Log2n) 即Ѳ(nLog2n).**

递归调用时, 最关键的就是递归调用栈的深度,我们也可以理解为分治算法中,被分成的段数。 也就是步骤中的第1步中所分成的子序列的个数。
假设这个递归调用深度 为D(n)。假设每次处理一个具体子序列时,所用的时间复杂度为C。(相当于这一层总的处理时间)
对于快速排序, C: 一个子项的处理时间f(n), D(n): 递归层数, 树高, Log2n,所以最终的结果是:f(nLog2n).
对于汉诺塔, C: 一个子项的处理时间f(1), D(n): 递归层数, 树高,2n-1, 故最终结果是: f(2n)

常见场景

快速排序,归并排序,汉诺塔,二分搜索,Fibonacci数列,Strassen矩阵乘法,最大元、最小元,最近点对问题,最大子序列和,寻找凸包,快速傅里叶变换等等。
最重要的就是分解成的子问题和原问题的类型相同,然后递归解决子问题,合并所有子问题的解取最终答案。

所有常见排序算法可看博客内部排序十大算法十分详细。
归并排序
在这里插入图片描述
快速排序
快速排序-总体思想
1.从数列中取出一个数作为基准数
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
3.再对左右区间重复第二步,直到各区间只有一个数
快速排序-编程思想
快速排序编程的难点就是如何把数组按照基准点排序,共有两种方法,填坑法,交换法
填坑法
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
交换法
举个例子:对5,3,8,6,4这个无序序列进行快速排序,思路是右指针找比基准数小的,左指针找比基准数大的,交换之。

最近点对问题
分解
对所有的点按照x坐标从小到大排序
根据下标进行分割,使点集分成两个集合
解决
递归寻找PL和PR中的最近点对
设其找到的最近点对的距离分别是δL和 δR
置δ=min(δL, δR)
合并
可能并不是δ,存在这样的情况,一个点在PL中,另一个点在PR中,而这两点之间的距离小于δ
如何检查呢?
只考虑分割线两侧距离各为δ的点,继续压缩点的范围

最大子序列和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
举例: [-2,1,-3,4,-1,2,1,-5,4]输出;6
因为连续子数组 [4,-1,2,1] 的和最大,为 6。

思路:它的最大子序和要么在左半边,要么在右半边,要么是穿过中间。对于左右边的序列,情况也是一样,因此可以用递归处理。中间部分的则可以直接计算出来,时间复杂度应该是 O(nlogn)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        #递归终止条件
        if n == 1:
            return nums[0]
        else:
            #递归计算左半边最大子序和
            max_left = self.maxSubArray(nums[0:len(nums) // 2])
            #递归计算右半边最大子序和
            max_right = self.maxSubArray(nums[len(nums) // 2:len(nums)])
        
        #计算中间的最大子序和,从右到左计算左边的最大子序和,从左到右计算右边的最大子序和,再相加
        max_l = nums[len(nums) // 2 - 1]#左侧最右边
        tmp = 0

        for i in range(len(nums) // 2 - 1, -1, -1):#步长1,负座标,即每次往左走一步
            tmp += nums[i]
            max_l = max(tmp, max_l)
        max_r = nums[len(nums) // 2]
        tmp = 0
        for i in range(len(nums) // 2, len(nums)):
            tmp += nums[i]
            max_r = max(tmp, max_r)
        #返回三个中的最大值
        return max(max_right,max_left,max_l+max_r)

寻找凸包
1.寻找最左下点,其他顶点与它连线
2.按夹角从小到大依次排序
3.从夹角最小开始,寻找凸包点,是的话入栈不出,不是的话入栈后需要弹出。
平面凸包
先找最左和最右端点,将平面分为两部分,在分为的两个部分和能组成面积最大的三角形的点连起来。
在这里插入图片描述
Strassen矩阵乘法
由8个乘法降为了7个
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值