分治算法(1)

引文

MapReduce(分治算法的应用) 是 Google 大数据处理的三驾马车之一,另外两个是 GFS 和 Bigtable。它在倒排索引、PageRank 计算、网页分析等搜索引擎相关的技术中都有大量的应用。

尽管开发一个 MapReduce 看起来很高深,感觉遥不可及。实际上,万变不离其宗,它的本质就是分治算法思想,分治算法。如何理解分治算法?为什么说 MapRedue 的本质就是分治算法呢?

1.主要思想

  • 分治算法的主要思想是将原问题分成若干个子问题,解决这些子问题再最终合并出原问题的答案。在计算过程中,子问题会被递归地分成更小的子问题,直到子问题满足边界条件。最后,算法会层层递回原问题的答案。

2.分治算法的步骤

1、将原问题分解成若干个性质相同的、相互独立的子问题。
2、递归地解决各子问题,直到子问题小到可以直接被解决。
3、逐层合并子问题的解,得到原问题的解。

3.分治法适用的情况

1、原问题的**计算复杂度**随着问题的规模的增加而增加。
2、原问题**能够被分解**成更小的子问题。
3、子问题的**结构和性质**与原问题一样,并且**相互独立**,子问题之间**不   包含**公共的子子问题。
4、原问题分解出的子问题的解**可以合并**为该问题的解。

引例:归并排序

我们通过一个例子更好地理解分治算法的原理。 我们即将升序排序以下数组:[10,6,2,9,5,1,12,4,0,4,1,2,1,3,2,9]。

在这里插入图片描述

我们将原问题分成了两个子问题,分别是排序 [10,6,2,9,5,1,12,4] 和排序 [0,4,1,2,1,3,2,9]。也就是说,我们将数组一分为二,分成了两个长度减半的数组。这样做的理由是,如果我们将两个子数组排序完毕,就可以通过双指针的方法不费事地将原数组排序。

双指针的方法如下:将两个指针分别放在两个子数组的第一个元素上,比较两个数字的大小,将较小的加入一个新的空数组,并将对应的指针挪到下一个位置,接着对比两个数字,一直将较小的数字加入新数组的队尾,直到某个指针到达队尾。如果还有剩余的数字,便将这些数字按顺序加入新数组的队尾。这样得到的新数组就是排序完毕的原数组。

问题是,如何将子数组排序?很好解决,利用同样的逻辑,我们可以将两个子数组也一分为二。只要将这四个子数组排序完毕,利用双指针的方法就能得到想要的答案。重复这个逻辑,我们同样也可以将那四个子数组递归排序。

我们不能永远地将问题传递下去,所以一旦子数组长度为一,我们就停止递归,将数组直接输出。在那之后,我们会通过双指针的方法得到排序完毕的,长度为二的子数组。得到长度为二的子数组后,我们会通过双指针的方法得到排序完毕的,长度为四的子数组,以此类推,直到得到原问题的解。

4.解题模板(伪代码)
def divide_conquer(problem, paraml, param2,...):
    # 不断切分的终止条件
    if problem is None:
        print_result
        return
    # 准备数据
    data=prepare_data(problem)
    # 将大问题拆分为小问题
    subproblems=split_problem(problem, data)
    # 处理小问题,得到子结果
    subresult1=self.divide_conquer(subproblems[0],p1,..…)
    subresult2=self.divide_conquer(subproblems[1],p1,...)
    subresult3=self.divide_conquer(subproblems[2],p1,.…)
    # 对子结果进行合并 得到最终结果
    result=process_result(subresult1, subresult2, subresult3,...)

5.力扣练习

50.pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100
  • 答案
class Solution:
    def myPow(self, x: float, n: int) -> float:
      # 方法1分治
        # if n<0:
        #     x = 1/x
        #     n = -n
        # if n == 0:
        #     return 1
        # if n%2 == 1:
        #     p = x*self.myPow(x, n-1)
        #     return p
        # else:
        #     return self.myPow(x**2,n/2)
       
         
        # 方法2直接**
        # return x**n


        # 方法3快速幂+递归
        def digui(n):
            if n == 0:
                return 1
            y = digui(n//2)
            return y*y if n%2==0 else y*y*x
        return digui(n) if n>= 0 else 1/digui(-n)

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
  • 答案
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 分治
        # if len(nums) == 1:
        #     return nums[0]
        # # 准备数据,切分条件
        # left = self.maxSubArray(nums[:len(nums)//2])
        # right = self.maxSubArray(nums[len(nums)//2:])
        # # 处理子问题得到子结果进行合并
        # # 对于left从右到左,对于right从做到右
        # max_l = nums[len(nums) // 2 - 1]  # max_l为该数组的最右边的元素
        # tmp = 0  # tmp用来记录连续子数组的和

        # for i in range(len(nums) // 2 - 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(left, right, max_l + max_r)

        # 动态规划
        # 从0到len(nums)-1,如果nums[i]>0,就加上nums[i],如果nums[i]<0,则nums[i-1]+nums[i]<nums         # [i],需要更新nums
        for i in range(1,len(nums)):
            nums[i] = max(nums[i-1]+nums[i], nums[i])
        return max(nums)
        
169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入: [3,2,3]
输出: 3

示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2
  • 答案
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # 分治
        # if not nums:
        #     return None
        # if len(nums)==1:
        #     return nums[0]
        # left = self.majorityElement(nums[:len(nums)//2])
        # right = self.majorityElement(nums[len(nums)//2:])
        # #合并子集结果
        # if left == right:
        #     return left
        # if nums.count(left) > nums.count(right):
        #     return left
        # else:
        #     return right


        # 哈希表直接计数
        # counts = collections.Counter(nums)
        # return max(counts.keys(), key=counts.get)

        # 排序
        # nums.sort()
        # return nums[len(nums)//2]

        # Boyer-Moore 投票算法
        count = 0
        contribution = None
        for num in nums:
            if count == 0:
                contribution = num
            count += (1 if num == contribution else -1)
        return contribution
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值