美文网首页
分治(Divide And Conquer)

分治(Divide And Conquer)

作者: Bel李玉 | 来源:发表于2020-08-23 22:48 被阅读0次

在上一篇文章中,我们介绍了贪婪策略,接下来我们将要探索分治策略。分治就是分而治之,它的一般步骤是

  • 1,将原问题分解成若干个规模较小的子问题(子问题和原问题的结构一样,只是规模不一样)。
  • 2,子问题又不断分解成规模更小的子问题,直到不能在分解(直到可以轻易计算出子问题的解)
  • 3,利用子问题的解推导出原问题的解。
    从上面的步骤中,我们可以看出分治策略非常适合用递归。
DivideAndConquer1.png

下面我们通过一个示例来探索该策略。

分析

Q1:给定一个长度为n的整数序列,求它的最大连续子序列和。

  • 比如 -2,-1, -3, 4,-1,2,1,-5,4的最大连续子序列和是 4 + (-1) + 2 + 1 = 6

解法1: 穷举出所有子自序列,分别计算子序列的和,取它们的最大值。

解法1实现
    static func maxSubArrayOne(_ nums: [Int]) -> Int {
        if nums.count == 0 {
            return 0
        }
        var maxTotalNum = Int.min  // 1

        for(beginIndex, _) in nums.enumerated() { // 2
            for (endIndex, _) in nums.enumerated().filter({ return $0.0 > beginIndex }) { // 3

                var tmpMaxNum = 0
                for index in beginIndex...endIndex { // 4
                    tmpMaxNum += nums[index]
                }
                if maxTotalNum < tmpMaxNum { // 5
                    maxTotalNum = tmpMaxNum
                }
            }
        }
        return maxTotalNum
    }
  • 1,将初始最小子序列和设置为最小整数。
  • 2,遍历数组,将索引 0 至 索引 count - 1 设置为数组的 beginIndex
  • 3,遍历数组,将大于beginIndex的索引值依次作为 endIndex
  • 4,取出子序列 [beginIndex endIndex],算出来该子序列的序列和。
  • 5,通过比较,获取子序列和和上一次循环得到子序列和的最大值。

该思路的空间复杂度为 O(1) ,时间复杂度为 O(n ^ 3)
在上面的解法中,我们存在一些重复计算:

 var tmpMaxNum = 0
 for index in beginIndex...endIndex { // 4
        tmpMaxNum += nums[index]
 }

当endIndex变化时,我们都需要遍历数组,来计算其子序列和,但在求[beginIndex endIndex - 1]时,我们就已经计算出该区间的子序列和了,所以,我们可以将其优化一下:

    static func maxSubArrayTwo(_ nums: [Int]) -> Int {
        if nums.count == 0 {
            return 0
        }
        var maxTotalNum = Int.min

        for(beginIndex, _) in nums.enumerated() {
            var subTotal = 0
            for (endIndex, _) in nums.enumerated().filter({ return $0.0 > beginIndex }) {
                subTotal += nums[endIndex]
                maxTotalNum =  max(maxTotalNum, subTotal)
            }
        }
        return maxTotalNum
    }

通过上面优化,我们减少了一次遍历,这样时间复杂度为O(n ^ 2)

解法2:采用分治的策略,将序列均匀地分割成2个子序列
分割成2部分:

  • [begin, end) = [begin, mid) + [mid, end), mid = (begin + end) / 2

假设[begin, end)的最大连续子序列和是 [i , j),那么它有 3 种可能

  • [i,j)存在于 [begin, mid)中,即 begin ≤ i < j < mid, 同时[i , j)也是 [begin, end)的最大连续子序列和。
  • [i,j)存在于 [mid, end)中,即 mid ≤ i < j < end, 同时[i , j)也是 [mid, end)的最大连续子序列和。
  • [i,j)一部分存在[begin, mid) 中,另一部分存在于[mid, end) 中
    1,[i,j) =[i,mid) + [mid, j)
    2,[i,mid) = [k, mid), begin ≤ k < mid,[k, mid),也是左边序列的最大子序列和
    3,[mid, j) = [mid, h), mid < k ≤ end, [mid,h),也是右边序列的最大子序列和
实现
    static func maxSubArrayThree(_ nums: [Int]) -> Int {
        if nums.count == 0 {
            return 0
        }

        return maxSubArray(nums, 0, nums.count)
    }

    private static func maxSubArray(_ nums:[Int], _ begin: Int, _ end: Int)  -> Int{
        if (end - begin < 2) {
            return nums[begin]
        }


        let mid = (begin + end) >> 1

        // 最大子序列和一部分在左边序列,一部分在右边序列的情况
        // 1:计算右半部分最大连续子序列和
        var rightMax = nums[mid]
        var rightSum = rightMax
        for i in mid..<end {
            rightSum += nums[i]
            rightMax = max(rightSum, rightMax)
        }

        // 2,计算左半部分最大连续子序列和
        var leftMax = nums[mid - 1]
        var leftSum = leftMax
        for i in (begin..<(mid - 1)).reversed() {
            leftSum += nums[i]
            leftMax = max(leftSum, leftMax)
        }
        let maxSubOne = leftMax + rightMax

        // 最大连续子序列和,序列都在左边的情况
        let maxSubTwo = maxSubArray(nums, begin, mid)
        // 最大连续子序列和,序列都在右边的情况
        let maxSubThree = maxSubArray(nums, mid, end)
        return max(max(maxSubTwo,maxSubThree)
            , maxSubOne)
    }

最后附上本文的相关代码DataAndAlgorim

相关文章

  • Divide and Conquer 分治法

    Divide and Conquer 分治法

  • Divide and Conquer

    算法学习之分治法(divide and conquer)

  • 分治(Divide And Conquer)

    在上一篇文章中,我们介绍了贪婪策略,接下来我们将要探索分治策略。分治就是分而治之,它的一般步骤是 1,将原问题分解...

  • 动态规划

    一、分治,回溯,递归,动态规划 1.1、递归的代码模板 1.2、分治(Divide & Conquer)的代码模板...

  • 分治法 Divide and Conquer

    解决的最轻,最重,矩阵乘法,大整数乘法以及排序(快速排序,归并算法)。快速傅立叶变换,Karatsuba乘法算法 ...

  • Divide and Conquer : 分治法

    知识点预习: 分治法: 先让左右子树去解决同样的问题, 然后得到结果之后, 再整合为整棵树的结果。 遍历法: 通过...

  • Divide and Conquer

    算法之 分治法 Divide and Conquer 分治法: 分治法的设计思想是:将一个难以直接解决的大问题,分...

  • 算法学习

    算法部分 二分搜索 Binary Search 分治 Divide Conquer 宽度优先搜索 Breadth ...

  • 算法部分

    二分搜索 Binary Search分治 Divide Conquer宽度优先搜索 Breadth First S...

  • 分治法,动态规划及贪心算法区别

    原文:分治法,动态规划及贪心算法区别 1.分治法 分治法(divide-and-conquer):将原问题划分成n...

网友评论

      本文标题:分治(Divide And Conquer)

      本文链接:https://www.haomeiwen.com/subject/hykwjktx.html