当前位置: 首页 > news >正文

背包问题【算法 07】

背包问题

请添加图片描述

背包问题是经典的计算机科学问题之一,涉及到如何在有限资源的约束下,选择最优的物品组合,以最大化收益。这个问题在现实中有广泛的应用,例如资源分配、物流调度和投资组合优化等。本文将详细介绍背包问题的定义、解决方法、常见变种,以及通过C语言的实现来帮助理解。

1. 背包问题简介

背包问题最早由丹麦数学家 Knuth 提出,其核心思想是:在一组物品中,每个物品都有一定的价值和重量,在背包的容量有限的前提下,选择哪些物品可以使得背包内物品的总价值最大化。

1.1 问题定义

背包问题可以表述为:给定 n 个物品,每个物品 i 有一个重量 w_i 和价值 v_i,在背包最大承重量 W 限制下,如何选择物品,使得装入背包的物品总重量不超过 W,且总价值最大。

公式化的定义如下:

  • 物品集合:{1, 2, ..., n}
  • 每个物品的重量:w_i
  • 每个物品的价值:v_i
  • 背包的容量:W

目标:在满足背包容量的前提下,最大化价值和:

max ∑ i = 1 n v i x i \text{max} \sum_{i=1}^n v_i x_i maxi=1nvixi
其中,x_i 表示第 i 个物品是否被选中,x_i = 1 表示选择,x_i = 0 表示不选择。

1.2 背包问题的类型

背包问题有多个变种,常见的类型包括:

  1. 0-1 背包问题:每个物品要么选择(1),要么不选择(0)。
  2. 完全背包问题:每个物品可以被选择多次。
  3. 分数背包问题:可以选择物品的一部分。

2. 0-1 背包问题

最经典的背包问题是 0-1 背包问题,即每个物品只能被选一次。此问题的解法包括动态规划(DP)和回溯法等。

2.1 动态规划解法

动态规划是一种自底向上的解决问题的方式,它可以有效地解决背包问题,避免暴力搜索带来的指数级时间复杂度。基本思想是构建一个二维数组 dp[i][w],表示前 i 个物品中能够装入重量为 w 的背包的最大价值。

2.1.1 状态转移方程

dp[i][w] 表示前 i 个物品能够放入容量为 w 的背包中的最大价值,则状态转移方程为:

d p [ i ] [ w ] = { d p [ i − 1 ] [ w ] , w < w i max ⁡ ( d p [ i − 1 ] [ w ] , d p [ i − 1 ] [ w − w i ] + v i ) , w ≥ w i dp[i][w] = \begin{cases} dp[i-1][w], & w < w_i \\ \max(dp[i-1][w], dp[i-1][w-w_i] + v_i), & w \geq w_i \end{cases} dp[i][w]={dp[i1][w],max(dp[i1][w],dp[i1][wwi]+vi),w<wiwwi
该方程表示如果当前物品 i 的重量超过背包剩余容量,则不能选择该物品;否则,我们可以选择不放入该物品或者放入该物品,取两者中的最大值。

2.2 动态规划算法实现

以下是 0-1 背包问题的 C 语言实现:

#include <stdio.h>// 定义最大物品数量和最大背包容量
#define MAX_ITEMS 100
#define MAX_WEIGHT 1000// 动态规划求解0-1背包问题
int knapsack(int n, int W, int weights[], int values[]) {int dp[MAX_ITEMS+1][MAX_WEIGHT+1] = {0};// 遍历每个物品for (int i = 1; i <= n; i++) {for (int w = W; w >= weights[i-1]; w--) {dp[i][w] = dp[i-1][w];if (w >= weights[i-1]) {dp[i][w] = (dp[i-1][w] > dp[i-1][w - weights[i-1]] + values[i-1]) ? dp[i-1][w] : (dp[i-1][w - weights[i-1]] + values[i-1]);}}}return dp[n][W];
}int main() {int n = 5;  // 物品数量int W = 10; // 背包容量int weights[] = {2, 2, 6, 5, 4};  // 每个物品的重量int values[] = {6, 3, 5, 4, 6};   // 每个物品的价值int maxValue = knapsack(n, W, weights, values);printf("背包可以装入的最大价值为: %d\n", maxValue);return 0;
}

2.3 案例分析

假设有5个物品,重量分别为 {2, 2, 6, 5, 4},价值分别为 {6, 3, 5, 4, 6},背包容量为 10

通过动态规划方法,得到的最大价值为 15。这是通过选择第 1、第 2 和第 5 个物品实现的,它们的总重量为 2+2+4=8,总价值为 6+3+6=15

3. 完全背包问题

在完全背包问题中,每个物品可以选择多次。这与 0-1 背包问题的区别在于,状态转移方程发生了变化,因而求解方法也有所不同。

3.1 状态转移方程

对于完全背包问题,状态转移方程为:

d p [ i ] [ w ] = max ⁡ ( d p [ i − 1 ] [ w ] , d p [ i ] [ w − w i ] + v i ) dp[i][w] = \max(dp[i-1][w], dp[i][w-w_i] + v_i) dp[i][w]=max(dp[i1][w],dp[i][wwi]+vi)
与 0-1 背包不同,选择当前物品时,需要考虑放入多次的情况。

3.2 完全背包问题的C语言实现

#include <stdio.h>int knapsack_complete(int n, int W, int weights[], int values[]) {int dp[MAX_WEIGHT+1] = {0};for (int i = 0; i < n; i++) {for (int w = weights[i]; w <= W; w++) {dp[w] = (dp[w] > dp[w - weights[i]] + values[i]) ? dp[w] : dp[w - weights[i]] + values[i];}}return dp[W];
}int main() {int n = 5;int W = 10;int weights[] = {2, 2, 6, 5, 4};int values[] = {6, 3, 5, 4, 6};int maxValue = knapsack_complete(n, W, weights, values);printf("完全背包可以装入的最大价值为: %d\n", maxValue);return 0;
}

3.3 案例分析

在同样的物品和背包容量情况下,完全背包的最大价值是 18,因为在完全背包中,可以选择物品 15 多次,从而获得更高的价值。

4. 分数背包问题

分数背包问题允许将物品分割开来,也就是说可以选择物品的一部分。通常使用贪心算法解决该问题,依据单位重量的价值(价值/重量)对物品进行排序,然后依次选择单位价值最高的物品直到背包满为止。

4.1 分数背包问题的案例分析

在分数背包问题中,物品可以被部分选择,这与 0-1 背包和完全背包不同。例如,假设有以下 3 个物品:

  • 物品 1:重量 10,价值 60
  • 物品 2:重量 20,价值 100
  • 物品 3:重量 30,价值 120

背包的容量为 50。我们可以计算每个物品的单位重量价值,即 value/weight 比率:

  • 物品 1:60/10 = 6
  • 物品 2:100/20 = 5
  • 物品 3:120/30 = 4

根据贪心算法的思想,应该优先选择单位价值最高的物品。具体过程如下:

  1. 选择物品 1(单位价值最高,6),将其全部放入背包。此时,背包剩余容量为 50 - 10 = 40,价值为 60
  2. 选择物品 2(单位价值次高,5),将其全部放入背包。此时,背包剩余容量为 40 - 20 = 20,价值增加为 60 + 100 = 160
  3. 选择物品 3(单位价值最低,4),但此时背包只剩下 20 的空间,因此只能选择物品 3 的一部分,价值为 120 * (20/30) = 80

因此,最终的最大价值为 60 + 100 + 80 = 240

4.2 分数背包问题的 C 语言实现

以下是分数背包问题的 C 语言实现:

#include <stdio.h>
#include <stdlib.h>typedef struct {int weight;int value;double ratio;
} Item;// 比较函数,用于根据单位价值对物品排序
int cmp(const void* a, const void* b) {return ((Item*)b)->ratio > ((Item*)a)->ratio ? 1 : -1;
}// 贪心算法解决分数背包问题
double fractional_knapsack(int n, int W, Item items[]) {// 根据单位价值排序qsort(items, n, sizeof(Item), cmp);double maxValue = 0.0;// 贪心地选择物品for (int i = 0; i < n && W > 0; i++) {if (W >= items[i].weight) {W -= items[i].weight;maxValue += items[i].value;} else {maxValue += items[i].value * ((double)W / items[i].weight);W = 0;}}return maxValue;
}int main() {int n = 3;  // 物品数量int W = 50; // 背包容量Item items[] = {{10, 60, 6.0},{20, 100, 5.0},{30, 120, 4.0}};double maxValue = fractional_knapsack(n, W, items);printf("分数背包可以获得的最大价值为: %.2f\n", maxValue);return 0;
}

4.3 分析与总结

通过贪心算法,我们可以有效解决分数背包问题。该算法的时间复杂度主要由排序决定,为 O(n log n),而解决过程为 O(n)。与动态规划相比,贪心算法的效率更高,但它只能用于分数背包问题,不能用于 0-1 背包问题。

5. 不同背包问题的比较

问题类型可选物品算法类型时间复杂度备注
0-1 背包问题每个物品只能选择一次动态规划O(nW)经典问题
完全背包问题每个物品可以选择多次动态规划O(nW)物品可多次选择
分数背包问题物品可以部分选择贪心算法O(n log n)需要排序

6. 背包问题的现实应用

背包问题在许多实际应用中有广泛的应用,例如:

  1. 资源分配问题:在有限的资源下,如何分配资源以最大化收益。
  2. 物流调度问题:如何在有限的运输空间内安排货物的装载,使得运输效益最大化。
  3. 投资组合问题:如何在有限的资金下,选择合适的投资组合以最大化收益。

背包问题的灵活性使得它可以应用于各种优化问题中,不仅仅局限于物理背包的装载问题。

7. 总结

背包问题作为组合优化中的经典问题,有多种变体和解决方法。本文详细介绍了 0-1 背包问题、完全背包问题和分数背包问题的概念、算法及实现,并通过案例分析加深对这些问题的理解。通过不同的解法,解决了物品选择的最大价值问题,这种优化思想在现实中有广泛的应用价值。

相关文章:

  • 自然语言处理系列三十二》 语义相似度》语义相似度概念及入门
  • Python爬虫-实现自动获取随机请求头User-Agent
  • ArcGIS高/低聚类(Getis-Ord General G)——探究人口空间格局的20年变迁
  • WPS关闭后,进程依然在后台运行的解决办法
  • AI绘画SD三分钟入门教程!秋叶大佬8月最新的Stable Diffusion整合包V4.9来了,完整安装部署教程奉上,附各种模型插件一次性用爽!
  • 云 VS 边缘计算,关系与区别是什么?
  • SIP协议之匿名呼叫
  • 【数据结构篇】~栈和队列(附源码)
  • 终端防火墙软件功能 | 在终端设备上启用防火墙!终端安全小课堂开讲啦
  • ubuntu安装minio
  • 【达梦数据库】审计功能开启审计记录查看定时删除
  • Elementui-Plus动态渲染图标icon
  • C# LinkedList
  • 全光谱日光模拟HUD阳光倒灌实验温升测试
  • vue 组件通信的解决方案
  • download使用浅析
  • ES学习笔记(12)--Symbol
  • Git学习与使用心得(1)—— 初始化
  • nginx 负载服务器优化
  • npx命令介绍
  • Rancher如何对接Ceph-RBD块存储
  • SQLServer之索引简介
  • Sublime text 3 3103 注册码
  • uni-app项目数字滚动
  • Vue ES6 Jade Scss Webpack Gulp
  • 聊聊redis的数据结构的应用
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 物联网链路协议
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 原生js练习题---第五课
  • 自动记录MySQL慢查询快照脚本
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​VRRP 虚拟路由冗余协议(华为)
  • !!Dom4j 学习笔记
  • ######## golang各章节终篇索引 ########
  • #13 yum、编译安装与sed命令的使用
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • (2024.6.23)最新版MAVEN的安装和配置教程(超详细)
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)母版页和相对路径
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • .NET CF命令行调试器MDbg入门(一)
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET Core 通过 Ef Core 操作 Mysql
  • .Net Web窗口页属性
  • .NET单元测试使用AutoFixture按需填充的方法总结
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • .NET建议使用的大小写命名原则
  • .Net中间语言BeforeFieldInit
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题