算法:分治法(详解及例题)


1. 简介

        分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)

        分治法可以通俗的解释为:把一片领土分解,分解为若干块小部分,然后一块块地占领征服,被分解的可以是不同的政治派别或是其他什么,然后让他们彼此异化。

        分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

2. 适用条件

分治法所能解决的问题一般具有以下几个特征:

  1. 该问题的规模缩小到一定的程度就可以容易地解决

  2. 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

  3. 利用该问题分解出的子问题的解可以合并为该问题的解;

  4. 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

3. 实现步骤

        分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

        如果原问题可分割成k个子问题,1<k≤n ,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。

        分治法在每一层递归上都有三个步骤:

  • 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  • 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
  • 合并:将各个子问题的解合并为原问题的解。
    在这里插入图片描述

4. 例题解析

4.1 二分查找

        二分查找是典型的分治算法的应用。二分查找需要一个默认的前提,那就是查找的数列是有序的。

二分查找的思路比较简单:

1) 选择一个标志 i 将集合分为二个子集合 (一般为中间位置)
2) 判断标志L( i )是否能与要查找的值value相等,相等则直接返回
3)否则判断L( i )与value的大小
4) 基于判断的结果决定下步是向左查找还是向右查找
5) 递归记性上面的步骤

# 子问题算法(子问题规模为 1, 判断是否为值value)
def is_in_list(init_list, value):
    return [False, True][init_list[0] == value]

# 分治法
def solve(init_list, value):
    lens = len(init_list)
    # 若问题规模等于 1,直接解决
    if lens == 1: 
        return is_in_list(init_list, value)
    # 分解(子问题规模为 n/2)
    left_list, right_list = init_list[:lens//2], init_list[lens//2:]   
    # 递归(树),分治,合并
    res =  solve(left_list, value) or solve(right_list, value)
    return res

4.2 最大子序和(LeetCode题)

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

示例:

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

class Solution(object):
    def maxSubArray(self, nums):
        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
        # 从 nums[len(nums) // 2 - 1] 到 nums[0]
        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(max_right,max_left,max_l+max_r)
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔的菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值