七.动态规划

1.从斐波那契数列看动态规划

递归方法解决斐波那契存在的问题:
子问题重复计算
非递归的解法就是一种动态规划(DP)的思想:
1.最优子结构/递推式
2.重复子问题的结果记录下来,避免重复计算

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Xiang Hai
# wechat: xiaoyou42952
"""
从斐波那契数列看动态规划
递归方法解决斐波那契存在的问题:
    子问题重复计算
非递归的解法就是一种动态规划(DP)的思想:
    1.最优子结构/递推式
    2.重复子问题的结果记录下来,避免重复计算
"""

def fibnacci(n):
    """递归方法"""
    if n == 1 or n == 2:
        return 1
    else:
        return fibnacci(n-1) + fibnacci(n-2)

print(fibnacci(10))

def fibnacci_no_recur(n):
    """非递归"""
    f = [0, 1, 1]
    if n > 2:
        for i in range(n-2):
            num = f[-1] + f[-2]
            f.append(num)
    return f[n]


print(fibnacci_no_recur(100))

2.钢条切割问题

某公司出售钢条,不同长度价格不同
问题:现有一段长度为n的钢条和价格表,求切割钢条方案,使得总收益最大?

思考,长度为n的钢条有多少种切割方案?
2^(n-1): 有n-1个可以切割的位置,可以切也可以不切,即是2的n-1次方

设长度为n的钢条切割后最优收益值为rn,可以得出递推式:
rn = max(pn, r1 + r(n-1), r2 + r(n-2), r3 + r(n-3), …, r(n-1) + r1)
第一个参数pn表示不切割
其他n-1个参数分别表示另外n-1种不同切割方案,对方案i=1,2,…,n-1
将钢条切割为长度为i和n-i两段
方案i的收益ri为其长度的最优收益
考察所有的i,选择其中收益最大的方案

最优子结构:
可以将求解规模为n的原问题,划分为规模更小的子问题:完成一次切割
后,可以将产生的两段钢条看成两个独立的钢条切割问题
组合两个子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大的,构成原问题的最优解
钢条切割刚好满足最优子结构:问题的最优解由相关子问题的最优解组合成,这些问题可以独立求解

钢条切割问题还存在更简单的递归求解方法(cut_rod_recursion_2):
从钢条左边切割下长度为i的一段,只对右边剩下的一段继续切割,左边的不再切割
递推式简化为 rn = max(pi + r(n-i), …) i取值从1到n
不做切割的方案就可以描述为: 左边一段长度为n,收益为pn,剩余一段长度为0, 收益为r0=0

动态规划解法: (时间复杂度 O(n^2))
1.每个子问题只求解一次,保存求解结果
2.之后需要该子问题,只要查询保存结果

自底向上求解

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Xiang Hai
# wechat: xiaoyou42952
"""
某公司出售钢条,不同长度价格不同
问题:现有一段长度为n的钢条和价格表,求切割钢条方案,使得总收益最大?

思考,长度为n的钢条有多少种切割方案?
    2^(n-1): 有n-1个可以切割的位置,可以切也可以不切,即是2的n-1次方

设长度为n的钢条切割后最优收益值为rn,可以得出递推式:
    rn = max(pn, r1 + r(n-1), r2 + r(n-2), r3 + r(n-3), ..., r(n-1) + r1)
第一个参数pn表示不切割
其他n-1个参数分别表示另外n-1种不同切割方案,对方案i=1,2,...,n-1
    将钢条切割为长度为i和n-i两段
    方案i的收益ri为其长度的最优收益
考察所有的i,选择其中收益最大的方案

最优子结构:
可以将求解规模为n的原问题,划分为规模更小的子问题:完成一次切割
后,可以将产生的两段钢条看成两个独立的钢条切割问题
组合两个子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大的,构成原问题的最优解
钢条切割刚好满足最优子结构:问题的最优解由相关子问题的最优解组合成,这些问题可以独立求解

钢条切割问题还存在更简单的递归求解方法(cut_rod_recursion_2):
从钢条左边切割下长度为i的一段,只对右边剩下的一段继续切割,左边的不再切割
递推式简化为 rn = max(pi + r(n-i), ...) i取值从1到n
不做切割的方案就可以描述为: 左边一段长度为n,收益为pn,剩余一段长度为0, 收益为r0=0


"""

p = [0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,33,36,39,40]


def cut_rod_recursion(p, n):
    """递归,自顶向下实现,不推荐,有大量重复计算"""
    if n == 0:
        return 0
    else:
        res = p[n]
        for i in range(1, n):
            res = max(res, cut_rod_recursion(p, i) + cut_rod_recursion(p, n-i))
        return res

print(cut_rod_recursion(p,10))


def cut_rod_recursion_2(p, n):
    """还是递归,自顶向下实现,但左边不切割,依然有大量重复计算"""
    if n == 0:
        return 0
    else:
        res = 0
        for i in range(1, n+1):
            res = max(res, p[i]+cut_rod_recursion(p, n-i))
        return res

print(cut_rod_recursion_2(p,10))


"""
动态规划解法: (时间复杂度 O(n^2))
1.每个子问题只求解一次,保存求解结果
2.之后需要该子问题,只要查询保存结果

自底向上求解
"""

def cut_rod_dp(p, n):
    r = [0]
    for i in range(1, n+1):
        res = 0
        for j in range(1, i+1):
            res = max(res, p[j] + r[i-j])
        r.append(res)
    return r[n]


print(cut_rod_dp(p, 20))

"""
如何修改动态规划算法,使其不仅输出最优解,还输出最优解的切割方案?
    对每个子问题,保存切割一次时左边切下的长度
"""

def cur_rod_extend(p, n):
    r = [0]
    s = [0]
    for i in range(1, n+1):
        res_r = 0  # 价格最大值
        res_s = 0  # 对应的左边不切割长度
        for j in range(1, i+1):
            if p[j] + r[i-j] > res_r:
                res_r = p[j] + r[i-j]
                res_s = j
        r.append(res_r)
        s.append(res_s)
    return r[n],s

def cut_rod_solution(p, n):
    r, s = cur_rod_extend(p, n)
    ans = []
    while n > 0:
        ans.append(s[n])
        n -= s[n]
    return r, ans


print(cut_rod_solution(p, 20))

3.最长公共子序列

最长公共子序列(LCS)
暴力穷举法的时间复杂度:O(2^(m+n))
最长公共子序列是否有最优子结构?

例如:要求a = “ABCBDAB” 与 b=“BDCABA"的LCS:
由于最后一位"B” != “A”
所以LCS(a,b)是LCS(a[:-1],b)与LCS(a,b[:-1])中更长的LCS

递推式:
c[i,j] = 0 if i = 0 or j = 0
c[i,j] = c[i-1,j-1]+1 if i,j>0 and xi = yi
c[i,j] = max(c[i,j-1],c[i-1,j]) if i,j>0 and xi != yi

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Xiang Hai
# wechat: xiaoyou42952
"""
最长公共子序列(LCS)
暴力穷举法的时间复杂度:O(2^(m+n))
最长公共子序列是否有最优子结构?
    有

例如:要求a = "ABCBDAB" 与 b="BDCABA"的LCS:
    由于最后一位"B" != "A"
    所以LCS(a,b)LCS(a[:-1],b)LCS(a,b[:-1])中更长的LCS

递推式:
    c[i,j] =    0                           if i = 0 or j = 0
                c[i-1,j-1]+1                if i,j>0 and xi = yi
                max(c[i,j-1],c[i-1,j])      if i,j>0 and xi != yi
"""


def lcs_length(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n+1)] for _ in range(m+1)]
    for i in range(1, m+1):
        for j in range(1, n+1):
            if x[i-1] == y[j-1]:
                c[i][j] = c[i-1][j-1] + 1
            else:
                c[i][j] = max(c[i-1][j], c[i][j-1])
    return c[m][n]


def lcs(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    b = [[0 for _ in range(n + 1)] for _ in range(m + 1)]  # 1 左上方 2 上方 3 左方
    for i in range(1, m+1):
        for j in range(1, n+1):
            if x[i-1] == y[j-1]:
                c[i][j] = c[i-1][j-1] + 1
                b[i][j] = 1
            elif c[i-1][j] > c[i][j-1]:
                c[i][j] = c[i-1][j]
                b[i][j] = 2
            else:
                c[i][j] = c[i][j-1]
                b[i][j] = 3
    return c[m][n], b


def lcs_traceback(x, y):
    c, b = lcs(x, y)
    i = len(x)
    j = len(y)
    res = []
    while i > 0 and j > 0:
        if b[i][j] == 1:
            res.append(x[i-1])
            i -= 1
            j -= 1
        elif b[i][j] == 2:
            i -= 1
        elif b[i][j] == 3:
            j -= 1
    return "".join(reversed(res))

print(lcs_traceback("ABCBDAB", "BDCABA"))

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值