实验五 0-1背包问题的算法设计

一、实验原理

1.1 背包问题

背包问题已经是一个很经典而且讨论很广泛的算法问题了。
背包问题泛指这类种问题:给定一组有固定价值和固定重量的物品,以及一个已知最大承重量的背包,求在不超过背包最大承重量的前提下,能放进背包里面的物品的最大总价值。具体各类背包问题可以分成以下 3 种不同的子问题。

1.1.1 0-1背包问题

问题描述:有编号分别为 a,b,c,d,e 的五件物品,它们的重量分别是 2,2,6,5,4,它们的价值分别是 6,3,5,4,6,每件物品数量只有一个,现在给你个承重为 10 的背包,如何让背包里装入的物品具有最大的价值总和?
特点:每个物品只有一件,选择放或者不放

1.1.2 完全背包问题

问题描述:有编号分别为 a,b,c,d 的四件物品,它们的重量分别是 2,3,4,7,它们的价值分别是 1,3,5,9,每件物品数量无限个,现在给你个承重为 10 的背包,如何让背包里装入的物品具有最大的价值总和?
特点:每个物品可以无限选用

1.1.3 多重背包问题

问题描述:有编号分别为 a,b,c 的三件物品,它们的重量分别是 1,2,2,它们的价值分别是 6,10,20,他们的数目分别是 10,5,2,现在给你个承重为 8 的背包,如何让背包里装入的物品具有最大的价值总和?
特点 :每个物品都有一定的数量

1.2 解决算法

1.2.1 动态规划算法

       动态规划原理:动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。
       动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。用动态规划方法解决 0-1 背包问题:
       步骤 1-找子问题:子问题必然是和物品有关的,对于每一个物品,有两种结果:能装下或者不能装下。
第一,包的容量比物品体积小,装不下,这时的最大价值和前 i-1 个物品的最大价值是一样的。
第二,还有足够的容量装下该物品,但是装了不一定大于当前相同体积的最优价值,所以要进行比较。由上述分析,子问题中物品数和背包容量都应当作为变量。因此子问题确定为背包容量为 j 时,求前 i 个物品所能达到最大价值。
       步骤 2-确定状态:由上述分析,“状态”对应的“值”即为背包容量为 j 时,求前 i个物品所能达到最大价值,设为 dp[i][j]。初始时,dp[0][j] (0<=j<=V)为 0,没有物品也就没有价值。
       步骤 3-确定状态转移方程:由上述分析,第 i 个物品的体积为 w,价值为 v,则状态转移方程为:
在这里插入图片描述

1.2.2 贪婪算法

       贪心法把一个复杂问题分解为一系列较为简单的局部最优选择,每一步选择都是对当前的一个扩展,直到获得问题的完整解。
       k-optimal 算法用来解决 0-1 背包问题:
       步骤 1-计算每种物品单位重量的价值 Vi/Wi ;
       步骤 2-依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。
       步骤 3-若将这种物品全部装入背包后,背包内的物品总重量未超过 C,则选择单位重量价值次高的物品并尽可能多地装入背包。
       依此策略一直地进行下去,直到背包装满为止。

1.2.3回溯法

       回溯法先确定解空间的结构,使用深度优先搜索,搜索路径一般沿树形结构进行,在搜索过程中,首先会判断所搜索的树结点是否包含问题的解,如果肯定不包含,则不再搜索以该结点为根的树结点,而向其祖先结点回溯;否则进入该子树,继续按深度优先策略搜索。
       运用回溯法解题通常包含以下三个步骤:
       a. 针对所给问题,定义问题的解空间;
       b. 确定易于搜索的解空间结构;
       c. 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;

1.2.4分支界定法

       分支限界法类似于回溯法,也是在问题的解空间上搜索问题解的算法。
       分支限界法首先要确定一个合理的限界函数(bound funciton),并根据限界函数确定目标函数的界[down ,up],按照广度优先策略或以最小耗费优先搜索问题的解空间树,在分直结点上依次扩展该结点的孩子结点,分别估算孩子结点的目标函数可能值,如果某孩子结点的目标函数可能超出目标函数的界,则将其丢弃;否则将其加入待处理结点表(简称 PT表),依次从表 PT 中选取使目标函数取得极值的结点成为当前扩展结点,重复上述过程,直到得到最优解。
       常见的两种分枝限界法包含队列式(FIFO)分支限界法和优先队列式分支限界法。

二、实验要求

算法设计:
输入物品数 n,背包容量 c,输入 n 个物品的重量、价值,在以上算法中任选两个实现最优解决 0-1 背包问题。
请问:所选算法的实现流程图或者伪代码是什么?比较时间复杂度和空间复杂度,得出什么结论

选择第一个算法动态规划和第二个算法贪婪算法来实现。

2.1动态规划实现

根据前面描述的动态规划原理和状态转移方程来解决0-1背包问题。

代码如下:

#include <iostream>
#include <vector>
using namespace std;
int main(){
    int n,capacity;
    cout << "请输入物品的数量" << endl;
    cin >> n;
    cout << "请输入背包的容量" << endl;
    cin >> capacity;
    vector<int> weight(n + 1,0);
    vector<int> value(n + 1,0);
    cout << "请输入物品的重量" << endl;
    for(int i = 1;i <= n;i++){
        cin >> weight[i];
    }
    cout << "请输入物品的价值" << endl;
    for(int i = 1;i <= n;i++){
        cin >> value[i];
    }
    vector<vector<int>> dp(n + 1,vector<int>(capacity + 1,0));
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= capacity;j++){
            if(weight[i] <= j){
                dp[i][j] = max(dp[i-1][j - weight[i]] + value[i],dp[i-1][j]);
            }
            else{
                dp[i][j] - dp[i-1][j];
            }
        }
    }
    cout << "最大价值总和为" << dp[n][capacity] << endl;
}

程序输出结果为:
在这里插入图片描述
输出结果正确,该代码时间复杂度O(n * capacity),空间复杂度也为O(n * capacity),该代码可以简化一下空间复杂度,因为选择与否一个物体是根据前一个物体的选择来进行状态转移的,因此可以采用滚动数组的方式来优化空间复杂度,可降为O(capacity)级别的空间复杂度滚动数组注意遍历顺序
滚动数组代码如下所示:

vector<int> dp1(capacity + 1,0);
    for(int i = 1;i <= n;i++){
        for(int j = capacity; j >= weight[i];j--){
            dp1[j] = max(dp1[j - weight[i]] + value[i],dp1[j]); 
        }
    }
    cout << "最大价值总和1为" << dp1[capacity] << endl;

2.2贪婪算法实现

根据算法思想,将物品按v/w进行排序,每次取出权值最大的物品看能否放入背包中,若能放入则放入,否则放入次大的物品。
代码如下所示:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int main(){
    int n,capacity;
    cout << "请输入物品的数量" << endl;
    cin >> n;
    cout << "请输入背包的容量" << endl;
    cin >> capacity;
    vector<int> weight(n + 1,0);
    vector<int> value(n + 1,0);
    int result = 0;
    cout << "请输入物品的重量" << endl;
    for(int i = 1;i <= n;i++){
        cin >> weight[i];
    }
    cout << "请输入物品的价值" << endl;
    for(int i = 1;i <= n;i++){
        cin >> value[i];
    }
    先按v/w的大小进行排序
    for(int i = 1;i < n;i++){
        for(int j = 1;j < n;j++){
           // cout << (double)value[j] / (double)weight[j]  << " " << (double)value[j + 1]/(double)weight[j + 1]<< endl;
            if((double)value[j] / (double)weight[j]  < (double)value[j + 1]/(double)weight[j + 1]){
                swap(weight[j],weight[j+1]);
                swap(value[j],value[j+1]);
            }
        }
    }
    for(int i = 1;i <= n;i++){
        if(capacity >= weight[i]){
            result += value[i];
            capacity -= weight[i];
        }
    }
    cout << "最大价值总和为" << result << endl;
}

本算法的时间复杂度主要在排序上,采用了冒泡排序的方式,因此时间复杂度为O(n^2),空间复杂度为O(1)。n为物品的数量
为了优化该算法的时间复杂度,采用了优先队列自定义排序规则的方式来实现,代码如下所示:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct cmp{
    bool operator()(pair<int,int>& a, pair<int,int>& b){
        return (double)a.second/(double)a.first < (double)b.second/(double)b.first;
    }
};
int main(){
    int n,capacity;
    cout << "请输入物品的数量" << endl;
    cin >> n;
    cout << "请输入背包的容量" << endl;
    cin >> capacity;
    vector<int> weight(n + 1,0);
    vector<int> value(n + 1,0);
    int result = 0;
    cout << "请输入物品的重量" << endl;
    for(int i = 1;i <= n;i++){
        cin >> weight[i];
    }
    cout << "请输入物品的价值" << endl;
    for(int i = 1;i <= n;i++){
        cin >> value[i];
    }
    priority_queue<int,vector<pair<int,int>>,cmp> queue;
    for(int i = 1;i <= n;i++){
        queue.push(make_pair(weight[i],value[i]));
    }
    while(!queue.empty()){
        cout << queue.top().first << " " <<  queue.top().second << endl;
        if(capacity >= queue.top().first){
            result += queue.top().second;
            capacity -= queue.top().first;
        }
        queue.pop();
    }
 
    cout << "最大价值总和为" << result << endl;
}

该方法时间复杂度为O(nlogn),空间复杂度为O(n),至此贪婪算法的实现也结束。
程序输出截图如下图所示:
在这里插入图片描述

2.3 结论

       动态规划算法由于考虑到了所有种可能的情况并存储,因此相较而言时间和空间复杂度肯定会高一下。(方法二如果采用快排重定义排序规则的方式,会进一步降低空间复杂度到O(logn)),因此虽然动规方法很好,但对于性能要求高的需求来说可以适当采用其它方法。


总结

       本实验主要针对0-1背包的问题,采用不同种算法思想来解决。本实验介绍了四种方法,我主要采用了第一种动态规划和第二种贪婪算法来实现,提升了对该问题以及有关算法的了解程度。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值