Knapsack Problem

背包问题解析
本文详细介绍了三种背包问题(01背包、完全背包、分数背包)的动态规划解决方案,并提供了具体的算法实现示例。针对每种背包问题的特点,给出了状态转移方程及优化技巧。

背包九讲


0-1 knapsack problem

01背包-牛客网

已知一个背包最多能容纳体积之和为V的物品,现有 n 个物品,第 i 个物品的体积为 vi , 重量为 wi,求当前背包最多能装多大重量的物品?

使用动态规划来解决01背包问题:

首先定义dp数组:dp[i][j] := 0-i号物品面对容量为j的背包所能获得的最大重量

状态转移方程:

d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , 容量不足,不放入物品 i m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) , 考虑拿这件物品能否获得更大重量 dp[i][j]= \begin{cases} dp[i-1][j],\quad 容量不足,不放入物品i\\ max(dp[i - 1][j], dp[i - 1][ j - v[i]] + w[i]), \quad 考虑拿这件物品能否获得更大重量 \end{cases} dp[i][j]={dp[i1][j],容量不足,不放入物品imax(dp[i1][j],dp[i1][jv[i]]+w[i]),考虑拿这件物品能否获得更大重量

class Solution {
public:
    int knapsack(int V, int n, vector<vector<int> >& vw) {
        vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0));
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= V; ++j) {
                if (j < vw[i - 1][0]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - vw[i - 1][0]] + vw[i - 1][1]);
                }
            }
        }
        return dp[n][V];
    }
};

由于dp问题具有无后效性,可以采用滚动数组的方式节省内存,此时状态转移方程化为:

if j >= v[i]:
	dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

Charm Bracelet:定义一维的dp数组,每次从后向前刷新dp数组:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> W(N, 0);
    vector<int> D(N, 0);
    vector<int> dp(M + 1, 0);
    for (int i = 0; i < N; ++i) {
        cin >> W[i] >> D[i];
    }
    for (int i = 0; i < N; ++i) {
        for (int j = M; j >= 1; --j) {
            if (j >= W[i]) {
                dp[j] = max(dp[j], dp[j - W[i]] + D[i]);
            }
        }
    }
    cout << dp[M] << endl;
    return 0;
}

full knapsack problem

完全背包-牛客网

你有一个背包,最多能容纳的体积是V。现在有n种物品,每种物品有任意多个,第 i 种物品的体积为vi,价值为wi,求这个背包至多能装多大价值的物品?

完全背包的状态转移方程和01背包极其相似:

d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , 容量不足,不放入物品 i m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − v [ i ] ] + w [ i ] ) , 考虑拿这件物品能否获得更大重量 dp[i][j]= \begin{cases} dp[i-1][j],\quad 容量不足,不放入物品i\\ max(dp[i - 1][j], dp[i][ j - v[i]] + w[i]), \quad 考虑拿这件物品能否获得更大重量 \end{cases} dp[i][j]={dp[i1][j],容量不足,不放入物品imax(dp[i1][j],dp[i][jv[i]]+w[i]),考虑拿这件物品能否获得更大重量
而且,考虑使用滚动数组压缩空间,完全背包和01背包的状态转移方程是一样的。区别在于dp数组的遍历方式:完全背包问题必须顺序推导dp数组,而01背包采用逆序推导dp数组

if j >= v[i]:
	dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
class Solution {
  public:
    vector<int> knapsack(int v, int n, vector<vector<int> >& nums) {
        // write code here
        vector<int> res;
        vector<int> dp(v + 1, 0);
        vector<int> pack(v + 1, -255);
        pack[0] = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j <= v; ++j) {
                if (j >= nums[i][0]){
                    dp[j] = max(dp[j], dp[j - nums[i][0]] + nums[i][1]);
                    pack[j] = max(pack[j], pack[j - nums[i][0]] + nums[i][1]);	//只考虑能从0一步步跳到v的w
                }
            }
        }
        res.push_back(dp[v]);
        if (pack[v] > 0) {
            res.push_back(pack[v]);
        } else {
            res.push_back(0);
        }
        return res;
    }
};

Piggy-Bank


fractional knapsack problem

与上面的两种背包问题不同,分数背包问题(物品可以被任意分割)比较简单,可以用贪心算法解决:每次都选择单位价值最大的物品装入背包,如果未满,则选择下一个价值次大的物品装入背包

圣诞老人的礼物-百练

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;

#define MAXN (100+10)

struct Box {
	int v;
	int w;
	double density;
	Box() {}
	Box(int vv, int ww) :v(vv), w(ww), density(double(v) / w) {}
	bool operator<(const Box& b) {
		return density < b.density;
	}
};

int n, weigth;
double total_w = 0;
double total_v = 0;
Box boxes[MAXN];

int main() {
	cin >> n >> weigth;
	int v, w;
	for (int i = 0; i < n; ++i) {
		cin >> v >> w;
		boxes[i] = Box(v, w);
	}
	sort(boxes, boxes + n);
	for (int i = n - 1; i >= 0; --i) {
		if (total_w + boxes[i].w < weigth) {
			total_w += boxes[i].w;
			total_v += boxes[i].v;
		}
		else {
			total_v += boxes[i].density * (weigth - total_w);
			total_w = weigth;
		}
	}
	printf("%.1f\n", total_v);
	return 0;
}

Dynamic programming is an algorithm suitable for solving the Knapsack problem. It avoids repeated calculations by decomposing complex problems into overlapping sub - problems and storing the solutions to the sub - problems. The core of dynamic programming is state definition and state transition equations [^1]. The general steps of using dynamic programming to solve the Knapsack problem are as follows: 1. **Define the state**: Usually, two - dimensional states are defined. For example, let `dp[i][j]` represent the maximum value that can be obtained when considering the first `i` items and the capacity of the knapsack is `j`. 2. **State transition equation**: For the 0 - 1 Knapsack problem, if the weight of the `i` - th item is `w[i]` and the value is `v[i]`, then the state transition equation is: - When `j < w[i]`, `dp[i][j]=dp[i - 1][j]` (the current item cannot be put into the knapsack). - When `j >= w[i]`, `dp[i][j]=max(dp[i - 1][j], dp[i - 1][j - w[i]]+v[i])` (choose whether to put the current item into the knapsack). Here is a simple Python code example for the 0 - 1 Knapsack problem: ```python def knapsack(weights, values, capacity): n = len(weights) dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)] for i in range(1, n + 1): for j in range(1, capacity + 1): if j < weights[i - 1]: dp[i][j] = dp[i - 1][j] else: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]) return dp[n][capacity] weights = [2, 3, 4, 5] values = [3, 4, 5, 6] capacity = 8 print(knapsack(weights, values, capacity)) ``` ### Application scenarios - **Resource allocation**: In project management, given a limited amount of resources (such as time, budget, manpower), and different tasks with different resource requirements and benefits, the Knapsack problem can be used to select the most profitable combination of tasks. - **Stock selection**: When an investor has a certain amount of funds and there are multiple stocks with different prices and expected returns, the Knapsack problem can be used to select the most profitable stock portfolio.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值