动态规划算法解决背包问题,算法分析与C语言代码实现,时间效率解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 动态规划算法解决背包问题,算法分析与C语言代码实现,时间效率解析

目录


动态规划算法

算法介绍与思想

例子理解:斐波那契数

背包问题

问题介绍

算法思路

时间效率分析

代码实现


正文


动态规划算法


算法介绍与思想


     动态规划(dynamic programming)是一种算法设计技术,它有着相当有趣的历史。作为一种使多阶段决策过程最优的通用方法,它是在20世纪50年代由一位卓越的美国数学家理查德·贝尔曼(Richard Bellman)发明的。因此,这个技术名字中的“programming”是计划和规划的意思,不是代表计算机中的编程。它不仅是应用数学中用来解决某类最优问题的重要工具,而且还在计算机领域中被当作一种通用的算法设计技术来使用。在这里,我们正是从这个角度来考虑这种技术的。


       如果问题是由交叠的子问题构成的,我们就可以用动态规划技术来解决它。一般来说,这样的子问题出现在对给定问题求解的递推关系中,这个递推关系中包含了相同类型的更小子问题的解。动态规划法建议,与其对交叠的子问题一次又一次地求解,还不如对每个较小的子问题只求解一次并把结果记在表中,这样就可以从表中得出原始问题的解。


例子理解:斐波那契数


       斐波那契数。我们可以发现它对这项技术做出了很好的阐述。斐波那契数是以下序列中的元素:

1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,...... ,

     它可以用一个简单的递推式和两个初始条件来定义:


      如果我们试图利用第一个递推式直接计算第n个斐波那契数F(n),可能必须对该函数的相同值重新计算好几遍。请注意,计算Fn)这个问题是以计算它的两个更小的交叠子问题F(n-1)和F(n -2)的形式来表达的。所以,我们可以简单地在一张一维表中填入n+1个Fn)的连续值。开始时,通过观察初始条件第二个递推式可以填入0和l,然后以式第一个递推式作为运算规则计算出其他所有的元素。显然,该数组的最后一个元素应该包含F(n)。这个非常简单的算法只需要一个单循环就能完成。


       请注意,实际上,如果只存储斐波那契序列中最后两个元素的值,就可以避免使用额外的数组来完成这个任务。这种现象并不罕见,而且我们会在本章中遇到更多这样的例子。虽然动态规划法的直接应用也可以解释成一种特殊类型的空间换时间权衡技术,但有时候一个动态规划算法经过改进可以避免使用额外的空间。


       某些算法无需计算出该序列前面所有的元素就可以给出第n个斐波那契数的值。然而,一般来说,一个算法如果基于经典的从底至上动态规划方法,那就需要解出给定问题的所有较小子问题。动态规划法的一个变化形式试图避免对不必要的子问题求解。


       但无论我们使用动态规划的经典的从底至上版本还是它基于记忆功能的从顶至下版本,设计这样一种算法的关键步骤还是相同的,即导出一个问题实例的递推关系,该递推关系包含该问题的更小(并且是交叠的)子实例的解。但像计算第n个斐波那契数这样,直接表现为公式第一个递推式的形式,可以说是这个规则的一个极少例外。


       由于动态规划的大多数应用都是求解最优化问题,因此我们需要指出这类应用中的一个一般性法则。理查德·贝尔曼称其为最优化法则(principle of optimality)。该法则认为最优化问题任一实例的最优解,都是由其子实例的最优解构成的。最优化法则在大多数情况下是成立的,尽管也有少数情况例外(一个相当罕见的例子,就是在图中找最长简单路径)。虽然在应用动态规划求解具体问题时,需要检查最优化法则是否适用,但在设计动态规划算法时,做一个这样的检查并不困难。


背包问题


问题介绍


       背包问题是一种组合优化的NP完全问题1..也就是说,没有多项式时间复杂度的解法。背包问题的基本形式是:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,我们如何选择,才能使得物品的总价值最高。


       根据物品是否可以重复选择,背包问题可以分为以下几种类型:


01背包问题:每种物品只有一个,可以选择放或不放。

完全背包问题:每种物品有无限个,可以任意选择放或不放。

多重背包问题:每种物品有有限个,可以选择放或不放。

       此外,还有—些其他的变形,例如恰好装满、求方案总数、求所有的方案等。


算法思路


       我们用设计一个背包问题的动态规划算法来作为本节的开始:给定n个重量为w1,…",wn,价值为v1,…, vn的物品和一个承重量为W的背包,求这些物品中最有价值的一个子集,并且要能够装到背包中。在这里假设所有的重量和背包的承重量都是正整数,而物品的数量不必是整数。


       为了设计一个动态规划算法,需要推导出一个递推关系,用较小子实例的解的形式来表示背包问题的实例的解。让我们来考虑一个由前i个物品(1≤i≤n )定义的实例,物品的重量分别为w1,…, wi,价值分别为v1,…, vi,背包的承重量为j(1≤j≤W)。设F(i,j)为该实例的最优解的物品总价值,也就是说,是能够放进承重量为j的背包中的前i个物品中最有价值子集的总价值。可以把前i个物品中能够放进承重量为j的背包中的子集分成两个类别:包括第 i个物品的子集和不包括第i个物品的子集。然后有下面的结论:


       (1)根据定义,在不包括第i个物品的子集中,最优子集的价值是F(i-1,j)。


       (2)在包括第i个物品的子集中(因此,j-wi≥0),最优子集是由该物品和前i-1个物品中能够放进承重量为j- w;的背包的最优子集组成。这种最优子集的总价值等于vi+F(i- 1,j - wi)。


       因此,在前i个物品中最优解的总价值等于这两个价值中的较大值。当然,如果第i个物品不能放进背包,从前i个物品中选出的最优子集的总价值等于从前i-1个物品中选出的最优子集的总价值。这个结果导致了下面这个递推式:


       我们可以很容易地如下定义初始条件:


       当j≥0时,F(0,j)= 0;当i≥0时,F(i, 0)=0


       我们的目标是求F(n, W),即n个给定物品中能够放进承重量为W的背包的子集的最大总价值以及最优子集本身。


       下图给出了涉及公式上面的递推式和公式初始条件的物品总价值。当i, j >0时,为了计算第i行第j列的单元格F(i,j),我们拿前一行同一列的单元格与vi加上前一行左边wi列的单元格的和做比较,计算出两者的较大值。这个表格既可以逐行填,也可以逐列填。


时间效率分析


       该算法的时间效率和空间效率都属于0(nW)。用来求最优解的具体组成的时间效率属于O(n)。


代码实现


#include <stdio.h>
#define max(a,b) ((a)>(b)?(a):(b))
// n: 物品个数
// w: 物品重量数组
// v: 物品价值数组
// C: 背包容量
// 返回: 最大总价值
int knapsack(int n, int w[], int v[], int C) {
  // dp[j] 表示容量为j的背包的最大价值
  int dp[C+1];
  // 初始化边界条件
  for (int j = 0; j <= C; j++) {
    dp[j] = 0; // 没有物品可选,价值为0
  }
  // 状态转移方程
  for (int i = 1; i <= n; i++) {
    for (int j = C; j >= w[i-1]; j--) { // 倒序遍历,避免覆盖之前的状态
      // 在放入和不放入第i个物品中选择最大价值
      dp[j] = max(dp[j], dp[j-w[i-1]] + v[i-1]);
    }
  }
  // 返回最终结果
  return dp[C];
}
int main() {
  // 测试数据
  int n = 4;
  int w[] = {2,3,4,5};    //测试用例
  int v[] = {3,4,5,6};
  int C = 8;
  // 调用函数
  int ans = knapsack(n,w,v,C);
  // 输出结果
  printf("The maximum value is %d\n", ans);
  return 0;
}


相关文章
|
10天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
90 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
1天前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
26天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
135 30
|
5天前
|
存储 监控 算法
企业内网监控系统中基于哈希表的 C# 算法解析
在企业内网监控系统中,哈希表作为一种高效的数据结构,能够快速处理大量网络连接和用户操作记录,确保网络安全与效率。通过C#代码示例展示了如何使用哈希表存储和管理用户的登录时间、访问IP及操作行为等信息,实现快速的查找、插入和删除操作。哈希表的应用显著提升了系统的实时性和准确性,尽管存在哈希冲突等问题,但通过合理设计哈希函数和冲突解决策略,可以确保系统稳定运行,为企业提供有力的安全保障。
|
26天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
115 14
|
30天前
|
存储 算法
深入解析PID控制算法:从理论到实践的完整指南
前言 大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论学习打下基础。 在很多的自动化控制领域。都会遇到PID控制算法,这种算法具有很好的控制模式,可以让系统具有很好的鲁棒性。 基本介绍 PID 深入理解 (1)闭环控制系统:讲解 PID 之前,我们先解释什么是闭环控制系统。简单说就是一个有输入有输出的系统,输入能影响输出。一般情况下,人们也称输出为反馈,因此也叫闭环反馈控制系统。比如恒温水池,输入就是加热功率,输出就是水温度;比如冷库,
233 15
|
30天前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
44 8
|
30天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
365 6
|
30天前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
36 5
|
30天前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
41 5

推荐镜像

更多