动态规划法及其优化

这篇具有很好参考价值的文章主要介绍了动态规划法及其优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在很多问题中,动态规划算法是我们的最优选择,比起递归算法,动态规划算法的时间复杂度和空间复杂度都更加优越,可以处理的数据规模更大。但是,动态优化算法的时间复杂度为O(N*V),也就是说,当需要处理的数据规模较大时,使用动态规划算法也存在超时的可能性,因此,我们需要在动态规划的基础上做出优化。

动态规划的优化方法包括:

1. 使用空间换时间:将中间结果缓存在数组中,避免重复计算。

2. 无后效性:假设问题可以分解为若干子问题,某些子问题的解可以不受其他子问题的解的影响,则可以去掉一些不必要的计算。

3. 剪枝:在搜索的过程中,利用一些条件限制最优解的范围,过滤掉不需要搜索的部分,提高性能。

4. 动态规划与贪心算法:当问题可以用贪心算法解决时,应优先使用贪心算法,它的时间复杂度要小于动态规划算法。

5. 构建最优解:利用最优子结构可以减少搜索的规模,进而提高搜索效率。

样例展示

下面用一个具体的样例记录以下动态优化的过程:

题目描述:

动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档

代码段:

# 解决0-1背包问题
def knapsack(N, V, w, v):
    # 初始化动态规划表
    dp_table = [[0 for j in range(V + 1)] for i in range(N + 1)]
    # 使用动态规划求解
    for i in range(1, N + 1):
        for j in range(1, V + 1):
            # 如果第i件物品的重量大于背包容量j,则不装入背包
            # 由于weight和value数组下标都是从0开始,故注意第i个物品的重量为w[i-1],价值为v[i-1]
            if w[i - 1] > j:
                dp_table[i][j] = dp_table[i - 1][j]
            else:
                dp_table[i][j] = max(dp_table[i - 1][j], dp_table[i - 1][j - w[i - 1]] + v[i - 1])
    # 返回最大价值
    return dp_table[N][V]
 
if __name__ == '__main__':
    #N:表示题目中物品的总类别
    #count:表明题目中所有物品的总数量
    N, V = map(int, input().split())
    w = []
    v = []
    count = 0
    for i in range(N):
        wi, vi, si = map(int, input().split())
        count += si
        # 物品数量可以大于1,因此循环si次将其加入物品
        for _ in range(si):
            w.append(wi)
            v.append(vi)
    print(knapsack(count, V, w, v))

这段代码在题目规定的时间内完成数据的计算,得到正确的结果。

题目升级:

动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档

显然,和上面的题目相比,题干没有任何的变化,只是处理数据的规模变大了,而在处理该规模的数据时,上面的代码显示超时,也就是说,上述动态规划算法需要优化。

优化方法:

  1. 空间复杂度优化

# 解决0-1背包问题
def knapsack(N, V, w, v):
    #对原始方法进行优化
    #原始方法使用的是二维数组
    #当物品数量和背包容量较大时,空间复杂度和时间复杂度为O(N*M),很容易就会导致运行超时
    #这意味着我们可以从动态规划表上去优化这个问题
    
    #根据背包容量声明意味数组
    db = [0 for i in range (V+1)]
    
    #for循环
    for i in range(N):
        #内层for循环为递减循环,且不会遍历到w[i]-1
        for j in range(V,w[i]-1,-1):
            db[j] = max(db[j],db[j-w[i]]+v[i])
            
    return db[V]
 
if __name__ == '__main__':
    #N:表示题目中物品的总类别
    #count:表明题目中所有物品的总数量
    N, V = map(int, input().split())
    w = []
    v = []
    count = 0
    for i in range(N):
        wi, vi, si = map(int, input().split())
        count += si
        # 物品数量可以大于1,因此循环si次将其加入物品
        for _ in range(si):
            w.append(wi)
            v.append(vi)
    print(knapsack(count, V, w, v))

上述代码将状态转移表由二维数组转换为一维数组,降低了代码对空间复杂度的要求,但是不难看出,虽然设置了一定的循环条件,代码的时间复杂度仍然是O(N*M),也就是,运行该题目仍然会超时。问题并没有解决。

动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档
动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档

两段代码均只能通过13组数据,因此,还需要在运行时间上进行优化。

  1. 时间复杂度优化

如何能够降低时间复杂度,我首先想到的是剪纸,通过一定的条件限制,比如二分法,将时间复杂度降维O(NlogM)。

# 解决0-1背包问题
#利用二分法来减少时间复杂度
#首先的前提是输入的物品重量要有序排列,不然二分法进行剪枝没有意义

#1.读取输入数据,需要对物品重量排序,但是又要保证质量与价值之间的对应关系,用一定的数据结构存储最佳
#2.对物品依照质量进行排序
#3.采用二分法实现时间复杂度的降低,编写Knapsack函数

#这种优化时间复杂度的方法确实存在,但是在该问题中,如果需要用到每一次考察物品之后背包的状态,那么这种选择性
#更新状态的方式必然是错误的,因为没有记录每一步更新之后的所有可能状态 ,导致再下一步的状态再不完全的基础上
#获得错误的下一转移状态,最终只会得到一个错误的输出
def knapsack(N, V, w, v):
    #使用单维数组优化
    db = [0 for i in range (V+1)]
    
    #for循环
    for i in range(N):
        # 使用二分搜索算法优化
        left, right = 0, V
        while left <= right:
            mid = (left + right) // 2
            if mid < w[i]:
                left = mid + 1
            else:
                right = mid - 1
            
            db[mid] = max(db[mid],db[mid-w[i]]+v[i])
            print(db)
            
    return db[V]
 
if __name__ == '__main__':
    #N:表示题目中物品的总类别
    #count:表明题目中所有物品的总数量
    N, V = map(int, input().split())
    w = []
    v = []
    count = 0
    for i in range(N):
        wi, vi, si = map(int, input().split())
        count += si
        # 物品数量可以大于1,因此循环si次将其加入物品
        for _ in range(si):
            w.append(wi)
            v.append(vi)
    print(knapsack(count, V, w, v))

代码运行结果:

动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档

通过运行结果可以看出,这种方法错误。


标记+递归法代码:

def knapsack(N, V, w, v):
    #使用二进制优化的方法
    #因为是0-1背包,每个物品只能选择一次
    #所以每个物品都可以看做一个二进制数
    #声明变量maxval存储最大价值
    maxval = 0 
    #声明变量mask存储选择的物品
    mask = 0 
    #for循环
    #i为index,主要用于定位数组
    for i in range(N):
        #使用按位与运算符&
        if (mask & (1 << i)) == 0:
            #使用按位或运算符|
            if V >= w[i]:
                maxval = max(maxval, v[i] + knapsack(N, V - w[i], w, v))
            mask |= (1 << i)
    return maxval
 
if __name__ == '__main__':
    #N:表示题目中物品的总类别
    #count:表明题目中所有物品的总数量
    N, V = map(int, input().split())
    w = []
    v = []
    count = 0
    for i in range(N):
        wi, vi, si = map(int, input().split())
        count += si
        # 物品数量可以大于1,因此循环si次将其加入物品
        for _ in range(si):
            w.append(wi)
            v.append(vi)
    print(knapsack(count, V, w, v))

这段代码表面上看上去和普通的递归法极为相似,但是实际上却有很大的不同。

普通递归法代码:

#读取物品的数量以及背包的最大容量
N,V = map(int,input().split())

#建立数组存储物品的重量及价值
w=[0]
v=[0]

for i in range(N):
    w_i,v_i = map(int,input().split())
    w.append(w_i)
    v.append(v_i)
    
def knapsack(n,cap):
    #设置递归结束条件
    if n == 0:
        return 0
    #当物品的重量大于背包容量时
    if w[n] > cap:
        return knapsack(n-1,cap)
    return max(knapsack(n-1,cap),knapsack(n-1,cap-w[n])+v[n])

print(knapsack(N,V))

不同之处的分析:

普通递归法:在没有if条件语句的限制下,递归的搜索空间为动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档。加上if语句,可以减少部分分枝,但是当背包容量较大时,if语句所起的限制作用有限,时间复杂度趋近于O(动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档)

标记+递归:二进制机制用于标记每个物品状态,放入或者未放入背包。这种情况下,递归的搜索空间被缩小为n!。时间复杂度大大减小。

实质上,这两种算法的区别就是深度优先遍历和广度优先遍历的区别。

但是,标记+递归的时间复杂度还是过高,无法通过题目要求。


二进制+动态规划:

#读取第一行输入,n为输入行数(不是物品总数),w为背包容量
n, m = map(int, input().split())

#v物品重量,w表示价值
v, w = [], []

#统计物品数量
cnt = 0

for i in range(1, n + 1):
    #读取输入数据
    a, b, s = map(int, input().split())
    k = 1
    #将同样的物品按照2的指数关系合并成一个更大的物品,主要用于减少物品的数量
    #关于我自己的理解为什么可以直接将多个小的物品合成一个大的物品放入存储队列
    #我开始的疑惑是假设这样一个情景,存在一个极大的物品,将其放入背包后,还剩一点空间可以放入部分小物品
    #但是由于在物品存入队列时,我们直接将小物品合成了大的物品放入,万一存在一种尴尬情况,即可以放入单个小
    #物品,但是不能放入较大的物品,那岂不是达不到价值最优
    #实际上,我假设的这种情况是不存在的,因为在合成物品时,合成的物品是由小到大合并的,所以当有n个小物品时
    #合成的顺序一定是a,2a,4a,8a...,这些合成后的小物品通过各种方式的组合,可以表示a~na之间的任何一个总质量
    while k <= s:
        cnt += 1
        v.append(a*k)
        w.append(b*k)
        s -= k
        k *= 2
        
    if s > 0:
        cnt += 1
        v.append(a * s)
        w.append(b * s)

n = cnt

#一维数组表示状态转移表
f = [0] * (m + 1)
#n经过处理之后,存储队列中总的物品数量
for i in range(1, n + 1):
    #m 背包总容量
    for j in range(m, v[i - 1] - 1, -1):
        f[j] = max(f[j], f[j - v[i - 1]] + w[i - 1])

print(f[m])

该方法通过测试。

动态优化算法,动态规划,性能优化,深度优先,广度优先,Powered by 金山文档

总结:对算法进行优化时,需要综合考虑时间和空间复杂度。文章来源地址https://www.toymoban.com/news/detail-754676.html

到了这里,关于动态规划法及其优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 6-1 求解资源分配问题(动态规划法)[PTA]

    6-1 求解资源分配问题(动态规划法) 某公司有3个商店A、B、C,拟将新招聘的5名员工分配给这3个商店,各商店得到新员工后,每年的赢利情况如下表所示,求分配给各商店各多少员工才能使公司的赢利最大。 函数接口定义: 裁判测试程序样例: 输入格式: 第一行输入商店数

    2024年02月12日
    浏览(73)
  • 最优控制理论 九、Bellman动态规划法用于最优控制

    尽管DP也是最优控制理论的三大基石之一,但长久以来,动态规划法(Dynamic Programming)被认为只能在较少控制变量的多阶段决策问题中使用,维数灾难使他不可能搜索得了整个连续最优控制问题的高维状态空间,因此仍然只能在一些维数较低的离散决策变量最优选择中取得较好

    2024年02月01日
    浏览(47)
  • 回溯法,分支限界法,动态规划法求解0-1背包问题(c++)

    问题描述 给定n种物品和一背包。物品i的重量是wi0,其价值为vi0,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 搜索空间: 子集树(完全二叉树) 约束函数(进包用): 如果当前背包中的物品总重量是cw,前面k-1件物品都已经决定是否进包,那

    2024年02月10日
    浏览(58)
  • [动态规划] 01背包问题及其优化

    题目描述 给一个能承重V的背包,和n件物品,我们用重量和价值的二元组来表示一个物品,第i件物品表示为(Vi,Wi),问:在背包不超重的情况下,得到物品的最大价值是多少? 输入 第一行输入两个数 V,n,分别代表背包的最大承重和物品数。 接下来n行,每行两个数Vi,W

    2024年02月03日
    浏览(44)
  • 动态规划的算法题以及其python实现

    动态规划(Dynamic Programming)是一种常用的算法设计技术,用于解决具有最优子结构性质和重叠子问题的问题。它通过将原问题分解为若干个子问题,并先求解子问题的最优解,然后利用子问题的最优解构建原问题的最优解。 动态规划算法通常包括以下几个 关键步骤 : 定义

    2024年02月07日
    浏览(36)
  • 机器学习&&深度学习——随机梯度下降算法(及其优化)

    在我们没有办法得到解析解的时候,我们可以用过梯度下降来进行优化,这种方法几乎可以所有深度学习模型。 关于优化的东西,我自己曾经研究过智能排班算法和优化,所以关于如何找局部最小值,以及如何跳出局部最小值的一些基本思想是有感触的,随机梯度算法和其优

    2024年02月15日
    浏览(45)
  • 【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和

    动态规划 状态机dp 性能优化 给你一个长度为 n 的整数数组 nums 和一个 正 整数 k 。 一个子序列的 能量 定义为子序列中 任意 两个元素的差值绝对值的 最小值 。 请你返回 nums 中长度 等于 k 的 所有 子序列的 能量和 。 由于答案可能会很大,将答案对 109 + 7 取余 后返回。 示

    2024年04月27日
    浏览(35)
  • 深度剖析动态规划算法:原理、优势与实战

    动态规划是一种优化技术,通常用于解决那些可以分解为子问题的问题。它的核心思想是将大问题分解成小问题,通过解决小问题来构建大问题的解。这种方法通常用于解决最优化问题,其中目标是找到最佳解决方案,通常是最大化或最小化某个值。 动态规划算法的核心原理

    2024年02月07日
    浏览(43)
  • 了解动态规划算法:原理、实现和优化指南

    动态规划(Dynamic Programming,简称 DP)是一种通过将原问题拆分成子问题并分别求解这些子问题来解决复杂问题的算法思想。 它通常用于求解优化问题,它的核心思想是将原问题分解成一系列的子问题,通过找到子问题之间的递推关系,可以避免重复计算,从而大幅提高计算

    2024年02月11日
    浏览(37)
  • SGD算法的优化特性及其在深度学习中的应用(OptimizationPropertiesandApplicat

    作者:禅与计算机程序设计艺术 SGD(Stochastic Gradient Descent)算法作为深度学习中最常用的优化算法之一,具有较好的全局收敛速度和稳定性。然而,在某些场景下,SGD算法的训练效率和泛化能力仍有待提高。本文将探讨SGD算法的优化特性及其在深度学习中的应用。 引言 1.1

    2024年02月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包