常用算法:问题求解与优化

0. 概述

在面对问题求解或优化(求最优解)需求时,提出解决方案的核心思想是:分解问题规模,直到问题规模小到可以用非常简单、直接的方式来解决为止(此规模,称为问题的基本情况)。

根据分解后得到的基本情况(种类)数量,及从整体(规模)问题到基本情况的演进规律,可分别采用分治、递归(可升级为动态规划,二者适用解决的问题情况相同)和贪心算法来求解问题,如下表所示:

整体问题分解结果适用算法结果情况
整体问题有多个独立的不同基本情况(子问题),且已知所有基本情况的并集就是整体问题本身分治全局最优解
整体问题只有一种基本情况,且已知整体问题可分解为更小规模的相同问题(直至基本情况规模)递归/动态规划全局最优解
整体问题只有一种基本情况,但不知道整体问题向基本情况的演进规律贪心局部最优解

1. 分治算法

将问题分为若干更小规模的部分,通过解决每一个小规模部分问题,并将结果汇总得到原问题的解

2. 递归算法

递归是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。

递归算法的实现过程为:递归函数“调用自身”,由整体问题减小问题规模向基本结束条件演进,到达基本情况后再逐层返回结果推广出整体规模问题的结果。其程序实现要点是:

  1. 递归算法必须有一个基本结束条件(最小规模问题的直接解决)
  2. 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
  3. 递归算法必须调用自身(解决减小了规模的相同问题)

递归调用的实现过程中,容易出现资源耗尽的错误。这是因为当一个函数被调用的时,系统会把调用时的现场数据压入到系统调用栈每次调用,压入栈的现场数据称为栈帧当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。所以当递归的层数太多时,因系统调用栈容量有限,容易出现资源耗尽的错误。

此时应检查程序中是否忘记设置基本结束条件,导致无限递归或者向基本结束条件演进太慢,使递归层数太多、调用栈溢出。若检查无误,在内存大小允许的情况下,可适当增大Python默认的调用栈容量大小(默认为1000层)。在Python内置的sys模块可以获取和调整最大递归深度,如下图所示:

在这里插入图片描述
代码实现

# 递归 v 1.0
coin_dic = {
    1: 1
    , 2: 1
    , 5: 1
    , 10: 1
}
#
def MakeChange(coin, dic):
    if coin in dic:
        return dic[coin]
    else:    	
        return min([
            1 + MakeChange(coin-v, dic) for v in dic if coin > v
        ])

# test
MakeChange(21, coin_dic, temp)

递归实现时,可通过“memoization(记忆化/函数值缓存)”的技术避免重复计算,提高算法计算效率

# 递归 v2.0:增加备忘录(memoization)
coin_dic = {
    1: 1
    , 2: 1
    , 5: 1
    , 10: 1
}
temp = coin_dic.copy()
#
def MakeChange(coin, dic, temp):
    if coin in temp:
        return temp[coin]
    else:
        temp[coin] = min([
            1 + MakeChange(coin-v, dic, temp) for v in dic if coin > v
        ])
    return temp[coin]

# test
MakeChange(21, coin_dic, temp)

3. 动态规划

动态规划较递归算法的主要改进是:直接由基本情况(最优子问题)开始逐步迭代,每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案。这与递归算法相比,省去了由整体问题分解到基本情况的过程,节省了现场数据压入到系统调用栈所需的存储空间,在时间复杂度和空间复杂度上有显著提升。值得注意的是:不是所有动态规划问题都可以简化掉备忘录的中间结果缓存空间,使算法空间复杂度下降到 O(1) 的。(比如下例硬币兑换问题中,就无法简化掉备忘录空间)

示例代码实现:

# 动态规划
coin_dic = {
    1: 1
    , 2: 1
    , 5: 1
    , 10: 1
}
def dpMakeChange(dic, coin):
    # 中间结果缓存
    temp = coin_dic
    # 规模演进
    for i in range(1, coin+1, 1):
        if i not in temp:
            temp[i] = min([
                1 + temp.get(i-v, 10*i) for v in dic
            ])
    return temp[coin]

# test
dpMakeChange(coin_dic, 11)

4. 贪心算法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值