首页 > 其他分享 >0-1背包问题详解-动态规划-两种方法

0-1背包问题详解-动态规划-两种方法

时间:2023-05-25 17:01:22浏览次数:35  
标签:背包 weight int MAX 详解 物品 最优 动态


问题描述:

给定n种物品和一背包。物品i的重量为wi,其价值为vi, 背包容量为c。问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大?

解析:

此问题形式化的描述是,给定c > 0, wi, vi, 1 <= i <= n(c为背包容量), 要找出一个n元0-1向量(x1, x2, ... , xn),xi ∈ {0, 1}, 1 <= i <= n, 使得∑ wixi (i 从 1 到 n 求和)<= c ,而且∑ wixi (i 从 1 到 n 求和)达到最大。因此0-1背包问题是一个特殊的整数规划问题。

方法1:

递归关系:(这里与课本的描述不同个人感觉课本的“加”比较难理解, 这里用的“减”, 相信我继续看下去QAQ, 方法2用课本的方法“加”)

设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是是在背包容量为j,可选物品为1, 2, 3, ..., i 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:

m[i][j] = max{m[i - 1][j], m[i - 1][j - w[i]] + v[i]}     j >= w[i];

            = m[i - 1][j]                                                    j < w[i];

递归关系流程化:

1:如果不放入第 i 件物品,则问题转换为“前i - 1件物品放入容量为 j 的背包中”;

2:如果放入第 i 件物品,则问题转化为“前i - 1件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i - 1][j], m[i - 1][j - w[i]] + v[i] }.

代码:

/*
输入样例 1:

3 10
4 3
5 4
6 5
输出为:

最优解为 : 11
选择的物品的序号为 :
2 3

输入样例 2:

5 10
6 2
3 2
5 6
4 5
6 4
输出为:

最优解为 : 15
选择的物品的序号为 :
1 2 5
*/
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX][MAX]; //解的容器
void Input()
{
    scanf("%d %d", &n, &c);
    for(int i = 1; i <= n; ++i)
        scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
    memset(m, 0, sizeof(m));
    for(int i = 1; i <= n; ++i) //逐行填表,i表示当前可选物品数,j表示当前背包的容量, 也就是从低到顶。 
    {
        for(int j = 1; j <= c; ++j)
        {
            if(j < weight[i])
                m[i][j] = m[i - 1][j];
            else
            {
                m[i][j] = max(m[i - 1][j], m[i - 1][j - weight[i]] + value[i]);
            }
        }
    }
}
//获取最优解(即设法将求得的最优解输出出来)
void Traceback()
{
    int cc = c;
    for(int i = n; i > 1; --i)
    {
        if(m[i][cc] == m[i - 1][cc])
            x[i] = 0;
        else
        {
           x[i] = 1;
           cc -= weight[i];
        }
    }
    if(cc >= weight[1])
        x[1] = 1;

}
void Output()
{
    cout << "最优解为 : " << m[n][c] << endl;
    cout << "选择的物品的序号为 :" << endl;
    for(int i = 1; i <= n; ++i)
        if(x[i] == 1)
         cout << i << " ";
    cout << endl;
}
int main()
{
    Input();
    Knapsack();
    Traceback();
    Output();
}


方法2:(解释见代码注释)


递归关系:

设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是背包容量为j,可选物品为i, i + 1, ... n 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:

m[i][j] = max{m[i + 1][j], m[i + 1][j - w[i]] + v[i]}     j >= w[i];

            = m[i + 1][j]                                                    j < w[i];

递归关系流程化:

1:如果不放入第 i 件物品,则问题转换为“i + 1, i + 2,  ..... , n件物品放入容量为 j 的背包中”;


2:如果放入第 i 件物品,则问题转化为“i + 1, i + 2,  ..... , n 件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i + 1][j],  m[i + 1][j - w[i]] + v[i]}.

与方法1的区别:

请先务必先看懂方法1~。

首先我们知道求最优解的过程就是填表m的过程。

第一行开始填表m,而方法2是从第n行开始填表m即(两种方法都是是从底往上求解,不过方法一是可选物品从1-> i(从左到右),方法二是可选物品从i -> n(从右向左)。这就是区别。而思路完全一样。请务必好好想想,然后就会恍然大悟٩(๑>◡<๑)۶ 

0-1背包问题详解-动态规划-两种方法_递归

代码:

总的来说方法2比方法1运行速度快。

/*
输入样例 1:

3 10
4 3
5 4
6 5
输出为:

最优解为 : 11
选择的物品的序号为 :
2 3

输入样例 2:

5 10
6 2
3 2
5 6
4 5
6 4
输出为:

最优解为 : 15
选择的物品的序号为 :
1 2 5
*/
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX][MAX]; //解的容器
void Input()
{
    scanf("%d %d", &n, &c);
    for(int i = 1; i <= n; ++i)
        scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
    int jMax = min(c, weight[n] - 1);

    //这一块填最后m表的最后一行
    /*
      解释一下就是:“在可选的物品只有n即最后一个物品n,包的容量为c时” 的最优解。
      第一个for循环:
      容易知道在背包容量为0 ~ weight[n] - 1的时候背包是放不进去物品n的,如果背包
      的容量小于物品n的质量,背包也是放不进去物品的,所以从weight[n] - 1 和 c 中
      选择一个较小的,然后m[n][0:jMax]的值为零
      第二个for循环:
      自然可知当背包容量大于weigh[n]的时候,由于其可选则的物品只有 物品n,因此m[n][weight[n]:c]
      的值全部为value[n].
    */
    for(int j = 0; j <= jMax; ++j)
        m[n][j] = 0;
    for(int j = weight[n]; j <= c; ++j)
        m[n][j] = value[n];

    //这一块是填m表的2 ~ n - 1行,容易理解
    for(int i = n - 1; i > 1; --i)
    {
        jMax = min(c, weight[i] - 1);
        for(int j = 0; j <= jMax; ++j)
            m[i][j] = m[i + 1][j];
        for(int j = weight[i]; j <= c; ++j)
            m[i][j] = max(m[i + 1][j], m[i + 1][j - weight[i]] + value[i]);
    }

    //这里是填m表的第一行,好好理解一下,不难,好好考虑一下 φ(>ω<*)
    m[1][c] = m[2][c];
    if(c >= weight[1])
        m[1][c] = max(m[1][c], m[2][c - weight[1]] + value[1]);
}
//获取最优解(即设法将求得的最优解输出出来)
void Traceback()
{
    int cc = c;
    for(int i = 1; i < n; i++)
        if(m[i][cc] == m[i + 1][cc])
           x[i] = 0;
        else
        {
            x[i] = 1;
            cc -= weight[i];
        }
        x[n] = (m[n][cc]) ? 1 : 0;
}
void Output()
{
    cout << "最优解为 : " << m[1][c] << endl;
    cout << "选择的物品的序号为 :" << endl;
    for(int i = 1; i <= n; ++i)
        if(x[i] == 1)
         cout << i << " ";
    cout << endl;
}
int main()
{
    Input();
    Knapsack();
    Traceback();
    Output();
//    cout << "*******" << endl;
//    for(int i = 1; i <= n; ++i)
//    {
//        for(int j = 1; j <= c; ++j)
//            cout << m[i][j] << " ";
//        cout << endl;
//    }
}

方法三:基于以方法一的路径压缩

思路:

定义m[j] : m[j]为背包容量为 j 时(注意不是剩余容量),背包装入的最大value。

解题流程:遍历 i (可选物品为 1 ~ i),m[j] = max{ m[j], m[j - weight[i]] + value[i] }

缺点:无法求出选中了哪些物品

#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX]; //解的容器
void Input()
{
    scanf("%d %d", &n, &c);
    for(int i = 1; i <= n; ++i)
        scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
    memset(m, 0, sizeof(m));
    for(int i = 1; i <= n; ++i)
    {
        for(int j = c; j >= weight[i]; --j)
        {
            m[j] = max(m[j], m[j - weight[i]] + value[i]);
        }
    }
}
void Output()
{
    cout << "最优解为 : " << m[c] << endl;
    cout << endl;
}
int main()
{
    Input();
    Knapsack();

    Output();
}



标签:背包,weight,int,MAX,详解,物品,最优,动态
From: https://blog.51cto.com/u_16129621/6350105

相关文章

  • 云原生第四周--kubernetes组件详解(下)
    ConfigmapConfigMap是一种API对象,用来将非机密性的数据保存到键值对中。使用时,Pods可以将其用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap将你的环境配置信息和容器镜像解耦,便于应用配置的修改。使用场景:通过Configmap给pod定义全局环境变量通过Confi......
  • 流水调度问题-动态规划-Johnson法则-两种方法
    问题描述:n个作业{0,1,2,…,n}在2台机器上M1和M2组成的流水线上完成加工。每个作业加工的顺序都是先在M1上加工,后在M2上加工。在两台机器上加工的时间分别为ai和bi。 确定这n个作业的加工顺序,使得从第一台作业开始加工,到最后一个作业完成加工所需要的时间最少。 递归关......
  • supervisor使用详解
    一介绍使用文档:http://supervisord.org/supervisor是Python开发的c/s服务,是Linux系统下的进程管理工具。可以监听、启动、停止、重启一个或多个进程用supervisor管理的进程,当一个进程意外被杀死,supervisor监听到进程死后,会自动将它重启,很方便的做到进程的自动恢复的功能,不在......
  • SpringBoot中使用@Scheduled实现定时任务通过读取配置文件动态开关
    场景SpringBoot中定时任务与异步定时任务的实现:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/117083609上面讲的通过@Scheduled注解实现简单定时任务的方式。如果定时任务有多个,不同业务场景下需要动态配置某个定时任务的开关。可以通过@ConditionalOnPropert......
  • 动态远程桌面如何用来做爬虫
    爬虫需要动态IP主要是为了避免被目标网站封禁或限制访问。如果使用固定IP进行爬取,很容易被目标网站识别出来并封禁,导致无法继续爬取数据。而使用动态IP可以让爬虫在不同的IP地址之间切换,降低被封禁的风险。此外,动态IP还可以帮助爬虫绕过一些反爬虫机制,提高爬取效率。远程桌面VPS可......
  • Nginx如何配置多个服务域名解析共用80端口详解
    前言由于公司一台服务器同时有多个服务,这些服务通过域名解析都希望监听80/443端口直接通过域名访问,比如有demo.test.com和product.test.com。这时候我们可以使用nginx的代理转发功能帮我们实现共用80/443端口的需求。备注:由于HTTP协议默认监听80端口,HTTPS协议默认监听443端口,所......
  • 8、动态规划基础
    内容来自刘宇波老师玩转算法面试1、什么是动态规划/***斐波那契数列FibonacciSequence*F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)*/publicstaticintfib(intn){if(n<=1)returnn;returnfib(n-1)+fib(n-2);}2、第一个动态规划......
  • 非极大值抑制(NMS)算法详解
    NMS(nonmaximumsuppression)即非极大值抑制,广泛应用于传统的特征提取和深度学习的目标检测算法中。NMS原理是通过筛选出局部极大值得到最优解。在2维边缘提取中体现在提取边缘轮廓后将一些梯度方向变化率较小的点筛选掉,避免造成干扰。在三维关键点检测中也起到重要作用,筛选掉特......
  • 【一文教你学会动态内存管理】
    1.为什么会存在动态内存分配?2.动态内存函数的介绍2.1malloc函数和free函数2.2calloc函数2.3realloc3.常见的动态内存错误3.1对NULL指针的解引用操作3.2对动态开辟空间的越界访问3.3对非动态开辟内存使用free释放3.4使用free释放一块动态开辟内存的一部分3.5对同一块动......
  • js基础之Promise详解
    1.是什么Promise是一种异步编程的解决方案,用于处理异步操作并返回结果。主要作用是解决回调函数嵌套(回调地狱)的问题,使异步操作更加清晰、易于理解和维护。2.怎么用Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当一个Promise被创建时,它的状态为pendin......