题目描述

有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
举一个例子:
有一个小偷他有一个容量为8的背包
物体的体积和价值如下所示:

物体编号 物体名称 体积 价值
1 手机 2 3
2 平板 3 4
3 相机 4 5
4 电脑 5 8

问小偷如何偷,偷的总价值最高?

思路

你站在小偷的角度思考一下,情况有这几种:

  • 第一种这个物品装不下。没法拿。
  • 第二种这个物品可以装下。但是到底拿不拿?

解决办法

用动态规划的方法来解决这个问题。
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

最优性原理是动态规划的基础,最优性原理是指"多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略。"

动态规划简言之就是: 将小问题依次递推到我们的最终问题。由于小问题都是最优解。
那么最终的问题一定也是最优解。

实战分析

题目的大致分析和动态规划的原理都知道了。接下来,我们就用上面的例子来实际的分析一波。
记f(k,w): 当背包容量为w,现有k件物品可以偷。
例: f(4,8) 的意思是有4件可以偷,背包的容量为8。
从物体编号4,3,2,1依次遍历


首先定义一些变量:Vk表示第 k个物品的价值,Wk表示第 k个物品的体积,定义f(k,w):当前背包容量 w,现有k件物品可以偷,前 k个物品可选的最佳组合对应的价值。
面对当前商品有两种可能性

  • 包的容量比该商品体积小,装不下,此时的价值与前k-1个的价值是一样的,即f(k,w)=f(k-1,w)
    简言之就是说: 小偷偷这个东西装不下,即不能选这个物品了,那么此时价值是不会多的,且偷的选项少了一个。
  • 还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即f(k,w)=max{f(k-1,w),f(k-1,w-wk)+vk}。

由上图可以得到如下状态转移方程:


这里需要解释一下,为什么能装的情况下,需要这样求解(这才是本问题的关键所在!):

可以这么理解,如果要到达f(k,w)这一个状态有几种方式?

肯定是两种,第一种是第k件商品没有装进去,第二种是第k件商品装进去了。没有装进去很好理解,就是f(k-1,w);装进去了怎么理解呢?如果装进去第k件商品,那么装入之前是什么状态,肯定是f(k-1,w-w(k))。由于最优性原理(上文讲到),f(k-1,w-w(k))就是前面决策造成的一种状态,后面的决策就要构成最优策略。两种情况进行比较,得出最优。

填表,首先初始化边界条件,f(0,w)=f(k,0)=0;
然后一行一行的填表:

  • 如,k=1,w=1,w(1)=2,v(1)=3,有w<w(1),故f(1,1)=f(1-1,1)=0;
  • 又如k=1,w=2,w(1)=2,v(1)=3,有w=w(1),故f(1,2)=max{
    f(1-1,2),f(1-1,2-w(1))+v(1) }=max{0,f(0,0)+3}=max{0,0+3}=3;
  • 如此下去,填到最后一个,k=4,w=8,w(4)=5,v(4)=8,有w>w(4),故f(4,8)=max{
    f(4-1,8),f(4-1,8-w(4))+v(4) }=max{f(3,8),f(3,3)+8}=max{9,4+8}=12
    根据状态转移方程可以得出如下表

    你如果仔细的看一下你会发现,表中的每一个空都是当前的最优解。
    而后面的最优解都是由前面的最优解依次递推出来的。
    由此每一个空都是当前下的最优解。
    例: f(3,5)代表的就是,当背包容量为5,前三个物品可以选时的最优解。
    f(4,8)就是当背包容量为8,有四个物品可以选时的最优解。

代码实现

#include<stdio.h>
#define N 5
#define W 9
int B[N][W]={0};
int w[5]={0,2,3,4,5};
int v[5]={0,3,4,5,8};
void max()
{int k,c;int value1,value2;for(k=1;k<N;k++)//1-4{for(c=1;c<W;c++)//1-8{if(w[k]>c)//如果说当前商品的重量大于背包剩余的数量,即:无法偷{B[k][c]=B[k-1][c];//价值等于上次的价值}else//可以偷{value1=B[k-1][c-w[k]]+v[k];//偷的话的价值value2=B[k-1][c];//不偷的话的价值if(value1>value2)//看哪个最优{B[k][c]=value1;}else{B[k][c]=value2;}}}}
}
int main()
{max();//printf("%d\n",B[4][8]);int i,j;for(i=0;i<N;i++){for(j=0;j<W;j++){printf("%d ",B[i][j]);}printf("\n");}
}

背包问题最优解回溯

通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:

f(k,w)=f(k-1,w)时,说明没有选择第k个商品,则回到f(k-1,w);
f(k,w)=f(k-1,w-w(k))+v(k)时,说明装了第k个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,
即回到f(k-1,w-w(k));
一直遍历到k=0结束为止,所有解的组成都会找到。


就拿上面的例子来说吧:

最优解为f(4,8)=12,而f(4,8)!=f(3,8)却有f(4,8)=f(3,8-w(4))+v(4)=f(3,3)+8=4+8=12,所以第4件商品被选中,并且回到f(3,8-w(4))=f(3,3);
有f(3,3)=f(2,3)=4,所以第3件商品没被选择,回到f(2,3);
而f(2,3)!=f(1,3)却有f(2,3)=f(1,3-w(2))+v(2)=f(1,0)+4=0+4=4,所以第2件商品被选中,并且回到f(1,3-w(2))=f(1,0);
有f(1,0)=f(0,0)=0,所以第1件商品没被选择。

最终代码实现

#include<stdio.h>#define N 5
//N代表共有几件可选的数量加1,因为数组的下标是从0开始的
//为了方便所以为 4+1 为5
#define W 9
//W为当前背包的容量,同样的为了方便加1int B[N][W]={0};//需要填的表
int intem[N]={0};//存放最优解的情况
int w[5]={0,2,3,4,5};//物体体积的数组
int v[5]={0,3,4,5,8};//物体价值的数组void max();
void print();
void find(int k,int c);void max()
{int k,c;//k代表有几个可以选,c代表当前的背包体积int value1,value2;for(k=1;k<N;k++)//1-4{for(c=1;c<W;c++)//1-8{if(w[k]>c)//如果说当前商品的重量大于背包剩余的数量,即:无法偷{B[k][c]=B[k-1][c];//价值等于上次的价值}else//可以偷{value1=B[k-1][c-w[k]]+v[k];//偷的话的价值value2=B[k-1][c];//不偷的话的价值if(value1>value2)//看哪个最优{B[k][c]=value1;}else{B[k][c]=value2;}}}}
}
void  find(int k,int c)//找最优解
{if(k>0){if(B[k][c]==B[k-1][c])//说明该物品没选{intem[k]=0;find(k-1,c);//往下接着递归查找}if( (c-w[k]>=0) && (B[k][c]==B[k][c-w[k]]+v[k]) )//说明该物体选了//{intem[k]=1;find(k-1,c-w[k]);}}
}
void print()
{int i;printf("最大价值为:%d\n",B[N-1][W-1]);printf("选的物体的编号为:");for(i=1;i<N;i++){if(intem[i]==1){printf("%d ",i);}}printf("\n");
}
int main()
{max();find(4,8);print();
}

效果图如下:

实战

光看不练假把式,我们看一道题实战一下。

看了题目你会发现这就是一个0/1背包问题。不过和我们上面讲的好像有点不一样。
但其实大同小异。
总结我们上面所学的动态规划(DP) 无非这几步:

  • 分析状态方程
  • 画表的边界
  • 画表

分析状态方程
我们上面的f(k,w)代表的是最大的价值。
而本问题问的是总的方法数,即最大的方法数。
故设 f(k,w): k代表有几个可选 w代表背包容量
f(k,w)代表当有k个物品可以选时,凑成体积为w的方法数
设Vk表示第 k个物品的体积

通过分析你会发现面对当前商品只有两种可能性:
拿与不拿

  • 不拿:包的容量比该商品体积小,装不下,此时的方法数与前k-1个的方法数是一样的,即f(k,w)=f(k-1,w)
    简言之就是说: 这个东西装不下,即不能选这个物品了,那么此时方法数是不会多的,且能选的物体数少了一个。
  • 拿:还有足够的容量可以装该商品.所以它的方法数等于拿的方法数+不拿的方法总数
    即f(k,w)=f(k-1,w)+f(k-1,w-v[k])

表的边界
当包的体积为0时啥也装不下。只有一种情况
故 f(k,0)=1;
当物体可选的为0时,一种情况也没有
故 f(0,w)=0;

最后画表的过程我这里就省略了。
下面直接上代码

#include<stdio.h>
#define N 21
#define W 41int f[N][W]={0};
int a[N]={0};
int main(void)
{int m;int k,w;while( scanf("%d",&m) != EOF ){for(k=1;k<=m;k++){scanf("%d",&a[k]);}for(w=0;w<W;w++)//初始化边界{f[0][w]=0;}for(k=0;k<N;k++)//初始化边界{f[k][0]=1;}for(k=1;k<=m;k++){for(w=1;w<W;w++){if(a[k]>w)//不能拿{f[k][w]=f[k-1][w];}else//可以拿{f[k][w]=f[k-1][w]+f[k-1][w-a[k]];//不拿的方法数+拿的方法数}}}printf("%d\n",f[m][40]);}return 0;
}

最后

本篇文章借鉴于这个大佬写的文章https://blog.csdn.net/qq_38410730/article/details/81667885
这个大佬写的很好。
我是按照我的理解在这位大佬的基础上做了补充。

让你轻松搞懂0-1背包问题(动态规划 C语言版)相关推荐

  1. LeetCode刷题复盘笔记—一文搞懂0 - 1背包之494. 目标和问题(动态规划系列第九篇)

    今日主要总结一下动态规划0-1背包的一道题目,494. 目标和问题 题目:494. 目标和 Leetcode题目地址 题目描述: 给你一个整数数组 nums 和一个整数 target . 向数组中的每 ...

  2. 【机器学习sklearn】两个例子轻松搞懂核密度估计KernelDensity

    前言 作业中遇到了需要使用KernelDensity的情况,但是网上的资料参差不齐,找了不短的时间却失望而归,最后还是靠着自己的理解才弄懂sklearn这个函数的使用,特此纪念. [机器学习sklea ...

  3. php service原理,轻松搞懂WebService工作原理

    用更简单的方式给大家谈谈WebService,让你更快更容易理解,希望对初学者有所帮助. WebService是基于网络的.分布式的模块化组件. 我们直接来看WebService的一个简易工作流程: ...

  4. proe常用c语言语句,带你轻松搞懂Proe条件语句

    原标题:带你轻松搞懂Proe条件语句 本文通过几个简单的例子介绍Proe中的条件语句,希望对你能有所帮助.Proe中使用的IF条件语句和C语言中的IF语句原理是一样的,其结构稍有差别.首先我们了解一下 ...

  5. 0/1背包问题——动态规划、回溯、分支限界法对比

    0/1背包问题--动态规划.回溯.分支限界法对比 2017.12.19 20:42:02 字数 3713 阅读 2820 目录 1.问题描述 1.1 问题描述 1.2 问题的数学表示(规划类问题,此种 ...

  6. Thermoflow GT Pro v19.0 Multilanguage-ISO 1CD(多语言版,包括简繁、体中文;电力及热电联产工业中热能工程软件)

    Thermoflow, Inc.产品: Thermoflow GT Pro v19.0  Multilanguage-ISO 1CD(多语言版,包括简繁.体中文:电力及热电联产工业中热能工程软件) T ...

  7. So easy ! 两句口号轻松搞懂Docker

    作为云计算IT圈里人, 如果还不知道Docker,那就算out啦.不过,在搞懂Docker之前,得先来了解一下Docker的成名之路! 从dotCloud到Docker,一"开"鸣 ...

  8. 一文搞懂0.1UF和10UF电容并联使用技巧

    摘要:搞电子的不知道小伙伴有没有被问到过,芯片附近放置的电容是多少?当你回答说是0.1uF,当你心里暗自庆幸还好自己知道的时候,面试官突然又问道为什么选取0.1uF?想必此时不少小伙伴都会想到,我看别 ...

  9. 0/1背包问题——动态规划方法

    1.定义 动态规划:把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解. 2.求解步骤 (1)找到状态转化条件 (2)归纳状态转移方程 (3)定义初始条件值 3.实例解析--0/1背包 ...

最新文章

  1. linux 命令窗口美化,美化你的命令行终端Terminal
  2. php7做了哪些优化,PHP语言学习之PHP7做了哪些优化
  3. C++中构造函数和析构函数可以抛出异常吗?
  4. 【原创】TLV5618芯片驱动程序
  5. Linux中iptables的用法
  6. 嵌入式linux gif 缩放_嵌入式环境动力监控主机
  7. Java 多线程详解(一)------概念的引入
  8. 分金币 (UVA 11300)
  9. Unity无缝切换场景
  10. UBI及EXT4文件系统
  11. 2007年浙江高考满分作文——行走在消逝中
  12. 使用JS获取客户端的IP地址
  13. 软件设计的哲学:第十六章 修改现有代码
  14. 【RC延迟电路与缓慢泄放电路 】 multisim 14.0仿真 参数计算
  15. 智能消息服务-数字短信使用FAQ简介: 数字短信是基于普通短信,通过一套编码技术实现的短信服务。它能把视频、音频、网页、GIF图、文字等多种表现形式通过多媒体短信形式触达用户。本文将带你了解阿里云通信
  16. PHPCMS短信接口替换
  17. 关于微信小程序云开发以及云开发实例展示
  18. python一次性封装多条sql语句(begin end)
  19. 三招通过Apollo和nacos的能力进行国际化热更新
  20. 对单模和多模块,光模块和电模块的理解

热门文章

  1. 做不了班主任,日本女教师给全班同学午餐下毒
  2. java 与操作系统底层交互_java程序员需要知道的底层知识(四)
  3. 数据结构--Tire树
  4. 女生数学不好能学计算机嘛,数学不太好,学计算机会痛苦吗?
  5. 转载:使用Pandas进行数据匹配
  6. 微信公众号(认证)订阅号和(认证)服务号的权限区别
  7. AI 数学基础知识-方向导数与梯度、范数矩阵、SVD分解、PCA、凸函数
  8. 男性的12种健康食物!
  9. appnium唤起软键盘
  10. 便携式双路CAN总线分析仪 CAN卡 USB卡 LCUSB-132B的功能介绍