背包算法详解

背包问题是一类常见的优化问题,其基本形式可以描述为:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,如何选择物品,使得所选物品的总价值最大。背包问题在现实生活中有广泛的应用,如货物装载、资源分配、金融投资等。

背包问题根据不同的限制条件和目标函数,可以分为多种类型,如0-1背包问题、完全背包问题、多重背包问题等。本文将重点介绍0-1背包问题的解法,并通过详细的C++代码实现来阐述其算法思想。

一、0-1背包问题

0-1背包问题是最基本的背包问题,它规定每种物品只能选择0个或1个,即不能选择部分物品。给定N种物品和一个容量为W的背包,每种物品都有自己的重量和价值,要求找出一种选择方案,使得背包内物品的总价值最大。

假设第i种物品的重量为w[i],价值为v[i],则0-1背包问题可以描述为:

给定N个物品的重量w[1], w[2], …, w[N]和价值v[1], v[2], …, v[N],以及背包的容量W,求一种选择方案,使得所选物品的总价值最大。

二、动态规划解法

动态规划是解决0-1背包问题的有效方法。我们可以定义一个二维数组dp[i][j],表示在前i种物品中,选择总重量不超过j的物品所能获得的最大价值。那么,dp[N][W]就是我们所求的结果。

动态规划的状态转移方程如下:

对于第i种物品,有两种选择:选或不选。

  1. 如果不选第i种物品,则dp[i][j] = dp[i-1][j],即前i种物品中选择总重量不超过j的物品所能获得的最大价值与前i-1种物品中选择总重量不超过j的物品所能获得的最大价值相同。

  2. 如果选择第i种物品,则需要判断其重量是否超过当前背包的剩余容量j。如果w[i] <= j,则dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]),即前i种物品中选择总重量不超过j的物品所能获得的最大价值为前i-1种物品中选择总重量不超过j的物品所能获得的最大价值(不选第i种物品)与前i-1种物品中选择总重量不超过j-w[i]的物品所能获得的最大价值(选第i种物品)中较大的一个。如果w[i] > j,则dp[i][j] = dp[i-1][j],因为背包无法装下第i种物品,所以最大价值与前i-1种物品中选择总重量不超过j的物品所能获得的最大价值相同。

根据上述状态转移方程,我们可以使用两层循环来求解dp数组。外层循环遍历物品,内层循环遍历背包容量。最终,dp[N][W]就是所求的最大价值。

三、C++代码实现

以下是使用C++实现0-1背包问题的动态规划解法的代码:

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int knapsack(int W, vector<int>& weights, vector<int>& values, int N) {vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));for (int i = 1; i <= N; i++) {for (int j = 1; j <= W; j++) {if (weights[i - 1] <= j) {dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);} else {dp[i][j] = dp[i - 1][j];}}}return dp[N][W];
}int main() {int W = 50; // 背包容量vector<int> weights = {10, 20, 30}; // 物品重量vector<int> values = {60, 100, 120}; // 物品价值int N = weights.size(); // 物品数量int max_value = knapsack(W, weights, values, N);cout << "The maximum value is: " << max_value << endl;return 0;}

四、优化空间复杂度

在上述的C++代码中,我们使用了一个二维数组dp来保存中间结果,其空间复杂度为O(NW),其中N为物品数量,W为背包容量。然而,我们可以观察到,在计算dp[i][j]时,只依赖于dp[i-1][...]的值,即当前行的值只与上一行的值有关。因此,我们可以将二维数组优化为一维数组,进一步降低空间复杂度。

下面是使用一维数组实现0-1背包问题的C++代码:

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int knapsack(int W, vector<int>& weights, vector<int>& values, int N) {vector<int> dp(W + 1, 0); // 使用一维数组dpfor (int i = 0; i < N; i++) {for (int j = W; j >= weights[i]; j--) { // 注意这里j从W开始递减dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);}}return dp[W];
}int main() {int W = 50; // 背包容量vector<int> weights = {10, 20, 30}; // 物品重量vector<int> values = {60, 100, 120}; // 物品价值int N = weights.size(); // 物品数量int max_value = knapsack(W, weights, values, N);cout << "The maximum value is: " << max_value << endl;return 0;
}

在这个优化后的代码中,我们注意到内层循环的j是从W开始递减的。这是因为我们在更新dp[j]时,用到了dp[j-weights[i]]的值,如果j从0开始递增,那么在更新dp[j]时,dp[j-weights[i]]可能已经被后面的更新所覆盖,导致结果不正确。而从W开始递减则可以保证在计算dp[j]时,dp[j-weights[i]]的值仍然是上一轮循环的结果。

五、完全背包问题

完全背包问题与0-1背包问题的区别在于,每种物品可以选择无限多个,而不是只能选择0个或1个。在0-1背包问题中,每种物品只有一个“选择”或“不选择”的状态,而在完全背包问题中,每种物品有多个“选择0个”、“选择1个”、“选择2个”…的状态。

为了解决这个问题,我们可以对0-1背包问题的解法稍作修改。在0-1背包问题中,内层循环是递减的,这是为了保证在计算dp[j]时,dp[j-weights[i]]的值是上一轮循环的结果。但在完全背包问题中,我们可以让内层循环递增,因为即使dp[j-weights[i]]被更新了,它表示的是选择更少物品的情况,而我们总是想选择更多的物品以获得更大的价值。

以下是使用一维数组实现完全背包问题的C++代码:

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int completeKnapsack(int W, vector<int>& weights, vector<int>& values, int N) {vector<int> dp(W + 1, 0);for (int i = 0; i < N; i++) {for (int j = weights[i]; j <= W; j++) { // 注意这里j从weights[i]开始递增dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);}}return dp[W];
}int main() {int W = 50; // 背包容量vector<int> weights = {10, 20, 30}; // 物品重量vector<int> values = {60, 100, 120}; // 物品价值int N = weights.size(); // 物品数量int max_value = completeKnapsack(W, weights, values, N);cout << "The maximum value in complete knapsack problem is: " << max_value << endl;return 0;
}

六、多重背包问题

多重背包问题中,每种物品有固定的数量,可以选择0个到该数量之间的任意多个。这个问题可以通过将多重背包转化为0-1背包或完全背包来解决。一种常见的做法是将每种物品拆分成多个重量和价值都相等的物品,每个物品只有一个,这样就转化为了0-1背包问题。另一种做法是使用二进制优化,将每种物品的数量表示成二进制数,然后依次将每个二进制位上的物品看作是一个新的物品,每种新的物品只有一个,这样就转化为了0-1背包问题。

七、分组背包问题

分组背包问题中,物品被划分为若干组,每组内的物品只能选择一个或者不选。这个问题可以通过将每组物品看作是一个新的“物品”,这个“物品”的重量和价值分别是该组内所有物品的重量和价值的集合,然后使用0-1背包的解法来解决。

假设有m组物品,每组有若干个物品,我们可以定义一个二维数组groupWeights[m][n]来保存每组物品的重量,其中n是组内物品的最大数量;类似地,定义一个二维数组groupValues[m][n]来保存每组物品的价值。然后,我们遍历每组物品,对于每组物品,我们遍历组内每个物品,并使用0-1背包的解法来更新当前背包的最大价值。

以下是使用一维数组实现分组背包问题的C++代码示例:

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;int groupKnapsack(int W, vector<vector<int>>& groupWeights, vector<vector<int>>& groupValues, int m) {vector<int> dp(W + 1, 0);for (int i = 0; i < m; i++) {for (int j = W; j >= 0; j--) { // 类似于0-1背包,从W开始递减for (int k = 0; k < groupWeights[i].size() && j >= groupWeights[i][k]; k++) {dp[j] = max(dp[j], dp[j - groupWeights[i][k]] + groupValues[i][k]);}}}return dp[W];
}int main() {int W = 50; // 背包容量vector<vector<int>> groupWeights = {{10, 20, 30}, {2, 3}, {5, 9, 10}}; // 每组物品的重量vector<vector<int>> groupValues = {{60, 100, 120}, {10, 20}, {20, 30, 40}}; // 每组物品的价值int m = groupWeights.size(); // 组的数量int max_value = groupKnapsack(W, groupWeights, groupValues, m);cout << "The maximum value in group knapsack problem is: " << max_value << endl;return 0;
}

八、有依赖的背包问题

有依赖的背包问题中,物品之间存在依赖关系,即一个物品被选中后,依赖于它的某些物品也必须被选中。这种问题通常可以通过树的形式来表示依赖关系,并使用树的遍历算法(如深度优先搜索)和背包算法结合来解决。

十、二维背包问题

二维背包问题中,物品有两个维度的属性,例如重量和体积,背包也有两个维度的限制,即容量和容积。这种问题可以通过将两个维度合并为一个维度来转化为传统的一维背包问题,或者使用多维动态规划来解决。

十一、总结与展望

背包问题是一类经典的优化问题,它涵盖了多种变种和扩展,每种问题都有其特定的解法和优化技巧。通过学习和掌握这些解法和技巧,我们可以更好地理解和解决实际应用中的优化问题。

未来,随着人工智能和机器学习等技术的不断发展,背包问题在更多领域中将得到应用。同时,随着算法和计算技术的不断进步,背包问题的求解方法也将更加高效和智能。因此,对于背包问题的研究具有重要的理论和实践意义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/20247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【STL源码剖析-空间配置器】stack、queue简单实现

举头天外望 无我这般人 目录 stack 的概述 stack 的实现 queue 的概述 queue 的实现 契子✨ 我们之前学过了 vector、list 这些 STL 的&#xff08;容器&#xff09; 而我们今天将要学习空间配置器 -- stack、queue&#xff0c;那什么是空间配置器呢&#xff1f; 简单来讲就是…

sqlite性能考量及使用(附可视化操作软件)

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

AI自动化办公:批量将Excel表格英文内容翻译为中文

有一个50列的表格&#xff0c;里面都是英文&#xff0c;要翻译成中文&#xff1a; 在ChatGPT中输入提示词&#xff1a; 你是一个开发AI大模型应用的Python编程专家&#xff0c;要完成以下任务的Python脚本&#xff1a; 打开Excel文件&#xff1a;"F:\AI自媒体内容\AI行业…

广东省青少年编程预选赛:挑战与机遇并存

广东省青少年编程预选赛&#xff1a;挑战与机遇并存 在数字化浪潮席卷而来的今天&#xff0c;编程技能已逐渐成为青少年必备的一项能力。广东省青少年编程预选赛作为一场汇集全省精英的竞技盛宴&#xff0c;不仅为青少年提供了一个展示才华的舞台&#xff0c;更是对他们逻辑思…

C++深度搜索

介绍 深度搜索是c的算法之一&#xff0c;简单来说就是“一路走到黑&#xff0c;不撞南墙不回头”就这样一条条把所有能走的路都走一遍&#xff0c;直到找出正确答案&#xff0c;有点类似于递归和枚举的结合体。 正文开始 迷宫出口 题目描述&#xff1a;一天Extense在森林里探…

HTML静态网页成品作业(HTML+CSS)——我的班级介绍网页(2个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

金属切削机床5G智能工厂工业物联数字孪生,推进制造业数字化转型

金属切削机床5G智能工厂工业物联数字孪生&#xff0c;推进制造业数字化转型。随着工业4.0时代的到来&#xff0c;制造业正面临着前所未有的变革与挑战。在这场变革中&#xff0c;金属切削机床智能工厂工业物联数字孪生平台正成为推动制造业数字化转型的重要力量。 数字孪生是指…

【vuejs】v-if和v-show的原理、异同、使用场景的分析

1. v-if 和 v-show 的共同点 1.1 作用效果的相似性 在Vue中&#xff0c;提供的v-if和v-show都是用来控制元素是否在页面上显示的条件指令。 当条件为true时&#xff0c;它们都会使元素可见&#xff1b; 当条件为false时&#xff0c;它们都会隐藏元素。 这一点在Vue的官方文…

香港云服务器好还是国内的好?

香港云服务器与国内云服务器各有其优点和缺点&#xff0c;选择哪种类型的云服务器主要取决于业务需求、用户群体、网络需求以及成本考虑。以下是对两者进行详细比较的内容。 首先&#xff0c;从网络速度和稳定性来看&#xff0c;香港云服务器具有独特的优势。由于香港是全球数据…

操作系统Linux的基本介绍

操作系统Linux的基本介绍如下&#xff1a; 一、概述 Linux是一个自由和开放源代码的类Unix操作系统&#xff0c;它基于POSIX和Unix的多用户、多任务、多线程和多CPU的操作系统。Linux最初是作为自由软件&#xff0c;由林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在1…

XSS攻击揭秘:从Cookie窃取到防御策略

实战中的窃取用户Cookie信息并保存到远程服务器的过程&#xff0c;通常涉及以下几个关键步骤&#xff1a; 实战步骤详解 1. 寻找XSS漏洞 首先&#xff0c;攻击者需要找到一个存在XSS漏洞的网站。这通常是通过代码审计、使用自动化工具扫描或手动测试来完成的。 2. 创建恶意…

企业文件加密:保障知识产权与客户隐私

在数字化时代&#xff0c;企业文件的安全成为了保护知识产权和客户隐私的关键。随着网络攻击和数据泄露事件的日益增多&#xff0c;企业必须采取强有力的措施来确保其敏感信息的安全。文件加密技术作为一项重要的数据保护手段&#xff0c;对于维护企业的竞争力和客户信任至关重…

STM32和ESP32哪个更适合初学者

对于初学者来说&#xff0c;STM32和ESP32都有各自的优点和适用场景&#xff0c;但考虑到初学者的学习曲线和资源可用性&#xff0c;以下是对两者的一些比较和建议&#xff1a; STM32 优点&#xff1a; 丰富的教程和社区支持&#xff1a;STM32拥有庞大的用户群体和活跃的社区…

图解支付系统的渠道路由设计

大家好&#xff0c;我是隐墨星辰&#xff0c;今天和大家聊聊渠道路由设计。 这篇文章主要讲清楚&#xff1a;渠道路由是什么&#xff0c;为什么需要渠道路由&#xff0c;渠道路由的几种形态&#xff0c;一个简洁而实用的基于规则的渠道路由设计。 注&#xff1a;有些公司称渠…

企业微信H5授权登录

在企业中如果需要在打开的网页里面携带用户的身份信息&#xff0c;第一步需要获取code参数 如何实现企业微信H5获取当前用户信息即accessToken&#xff1f; 1.在应用管理--》创建应用 2.创建好应用&#xff0c;点击应用主页-》设置-》网页-》将授权链接填上去 官方文档可以看…

wampserver的使用

wampserver的使用 文章目录 wampserver的使用1.启动2.目录3.基本操作 1.启动 WampServler有三种状态 服务器关闭状态&#xff0c;颜色为红色服务器开启&#xff0c;但是为离线状态&#xff0c;颜色为橙色&#xff0c;只有本机可以访问服务器开启&#xff0c;在线状态&#xf…

通过Bash脚本执行EXPDP实现本地和异地备份

在 Oracle 数据库管理中,定时执行备份是一个重要的任务,可以保证数据的安全性和可恢复性。本文将介绍如何使用 expdp 工具进行 Oracle 数据库备份,并使用 bash 脚本定时执行备份任务,并对备份文件进行压缩,传输。 准备工作 在执行备份任务之前,需要确保以下几点: 数据…

JAVA:JDK9到21,新特性了解

一、前言 随着Java技术的不断发展和进步&#xff0c;Java开发人员对于新特性的需求也越来越高。从JDK 9到JDK 21&#xff0c;Java发布了一系列重要的更新和改进&#xff0c;以满足开发人员的需求并提高应用程序的性能和安全性。本文将介绍JDK9到JDK 921的核心新特性&#xff0c…

Educational Codeforces Round 166 (Rated for Div. 2)题解(A,B,D)

今天真的巨抽象&#xff0c;第三题没做出来&#xff0c;但是第四题过了&#xff0c;也是准备上小分了&#xff0c;因为nnd不按那个分数&#xff0c;而是按照做题数&#xff0c;直接废了 A. Verify Password 题解&#xff1a;小丑水题一个人&#xff0c;按照ASCII码比较一遍直接…

SDK开发

为什么需要Starter&#xff1f; 理想情况:开发者只需关心调用哪些接口&#xff0c;传递哪些参数就跟调用自己写的代码一样简单。 开发starter的好处&#xff1a;开发者引入之后&#xff0c;可以直接在application.yml中写配置&#xff0c;自动创建客户端。 starter开发流程 …