01背包

视频地址:

https://www.bilibili.com/video/BV1U5411s7d7?

 

 

一,0-1 背包题目

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。其中,每件物品都只能选择一次。

 

 

二,错误的思考

之前曾经想到,可以求出每件物品的单价(即 价格/重量),在根据单价进行排序和选择。后来写出来答案不对,才发现这样做有个问题,即这里的物品并不是真的称斤买的。

例如,你背包空间剩下 4,此时还剩下两件物品没有决策,一个 w:1,v: 5,一个 w: 4,v: 6。这时,两件物品无法同时选择,答案很明显应该选第二个,但是在这种方法下,会选择第一个,因为第一个单价更高。

这种方法会出错的原因在于:到了最后,尽管你单价更高,但你的空间利用的不够。

错误的代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
#define N 100
struct Node
{
    int w, v;
    double av;
}kp[N];
bool vis[N];   // 标记这个物品是否偷过
bool cmp(Node a, Node b)
{
    return a.av - b.av < 1e-6;
}
int W, n;  // W 背包的最大空间  ,n 代表物品个数
int V;  // 最大价值
void knapsack_01()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            if (kp[j].w < W&&vis[j] == 0)
            {
                vis[j] = 1;
                W -= kp[j].w;
                V += kp[j].v;
            }
        }
    }
}
int main(void)
{
    scanf("%d%d", &n, &W);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d", &kp[i].w, &kp[i].v);
        kp[i].av = (double)kp[i].v / (double(kp[i].w));
    }
    sort(kp + 1, kp + n + 1, cmp);
    knapsack_01();

    printf("%d\n", V);
    
    system("pause");
    return 0;
}
/*测试数据:
5 20
2 3
3 4
4 5
5 8
9 10
*/
View Code

 

 

三,算法分析

  这种 01背包是比较一般性的动态规划问题,即可以由前面的状态堆出后面的状态。所以可以从函数关系和函数出口概括这种问题。( 。ớ ₃ờ)ھ 

设 B(k,w) :

  如果背包共计能装 w 重的商品,那么在只能选择前 k 个商品的前提下,背包能装的物品的最大的价值。

则有

  函数关系:

  在对 B(k, w) 进行分解时,可以将其分为三种情况:

    B(k, w)  =  max ①②③

      ① 为 B(k-1, w)        代表  第 k 件放不下,所以偷不了

      ② 为 B(k-1, w)        代表  第 k 件放得下,但选择不偷

      ③ 为 B(k-1, w-wk) + vk   代表  第 k 件放得下,且很选择偷

  函数出口:

    B(0, w) = 0  一个都不能偷,偷个寂寞

    B(k, 0)  = 0  一个都放不下,望洋兴叹

 

 

 四,代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define MAX(x,y) (x>y?x:y)
#define N 100
int b[N][N];
int w[N], v[N]; // 商品重量,商品价值
int n, m;        // 物品个数,背包体积
int dp(int k, int m)
{
    if (k == 0)
        return 0;
    if (w == 0)
        return 0;
    if (w[k] > m) // 放不下
        return dp(k - 1, m);
    return MAX(dp(k - 1, m - w[k]) + v[k], dp(k - 1, m));
}
void knapack_01()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (w[i] > j) // 放不下
                b[i][j] = b[i - 1][j];
            else
                b[i][j] = MAX(b[i - 1][j - w[i]] + v[i], b[i - 1][j]);
        }
    }
}
void show()
{
    printf("\nw == > ");
    for (int i = 0; i <= m; i++)
        printf("%3d", i);
    puts("");
    for (int i = 0; i <= n; i++)
    {
        printf("第%d件: ", i);
        for (int j = 0; j <= m; j++)
        {
            printf("%3d", b[i][j]);
        }puts("");
    }
    printf("当背包可装重 %d 的物品时,最多可偷 %d 价值的物品\n", m, dp(n, m));
}
int main(void)
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)    // 商品下标从1开始
        scanf("%d%d", &w[i], &v[i]);

    knapack_01(); // 01 背包
    show();

    system("pause");
    return 0;
}
/*测试数据:
5 20
2 3
3 4
4 5
5 8
9 10
*/
View Code

 

 

 

========== ========= ======== ======= ====== ===== ==== === == =

  清平乐 · 六盘山    毛润之

天高云淡,望断南飞雁。

不到长城非好汉,屈指行程二万。

六盘山上高峰,红旗漫卷西风。

今日长缨在手,何时缚住苍龙?

 
posted @ 2020-06-10 16:06  叫我妖道  阅读(704)  评论(0编辑  收藏  举报
~~加载中~~