【算法】0/1背包问题(蛮力+回溯+分支限界+贪心+非递归-动态规划)C++实现

分别用蛮力法、回溯法、分支限界法、贪心算法和动态规划法,求解0/1背包问题。

0-1背包问题:

有n件物品和一个容量为W的背包。第i件物品的价值是v[i],重量是w[i]。求解将哪些物品装入背包可使价值总和最大。所谓01背包,表示每一个物品只有一个,要么装入,要么不装入。

一、简单算法

最简单的算法是:蛮力法,即尝试各种可能的商品组合,并找出价值最高的组合。在这里插入图片描述
这样显然是可行的,但是速度非常慢。在只有3件商品的情况下,你需要计算8个不同的集合;当有4件商品的时候,你需要计算16个不同的集合。每增加一件商品,需要计算的集合数都将翻倍!这种算法的运行时间是O(2ⁿ),真的是慢如蜗牛。

1)实现代码

#include<stdio.h>
#include<vector>
using namespace std;
vector<vector<int>>ps; //存放幂集
//求1~n的幂集ps
void PSet(int n)
{
    vector<vector<int>>ps1;           //子幂集
    vector<vector<int>>::iterator it; //幂集迭代器
    vector<int>s;
    ps.push_back(s);              //向ps中添加{}空集合
    for (int i = 1; i <= n; i++) {        //循环添加1~n
        ps1 = ps;           //将上一步得到的幂集存放在ps1中
        for (it = ps1.begin(); it != ps1.end(); ++it) {
            (*it).push_back(i);               //每次循环在ps1的每个集合末尾添加i
        }
        for (it = ps1.begin(); it != ps1.end(); ++it) {
            ps.push_back(*it);
        }
    }
}

//求所有方案和最佳方案
void Search(int w[], int v[], int W)
{
    int count = 0;      //方案编号
    int sumw, sumv;    //当前方案的总重量和总价值
    int best_i, best_sumw = 0, best_sumv = 0;        //最佳方案编号、总重量和总价值
    vector<vector<int>>::iterator it;          //幂集迭代器
    vector<int>::iterator sit;    //幂集集合元素迭代器(子迭代器)
    printf("序号\t选择物品\t总重量\t总价值\t能否装入\n");
    for (it = ps.begin(); it != ps.end(); ++it) {
        printf("%d\t", count+1);
        sumw = sumv = 0;
        printf("{");
        for (sit = (*it).begin(); sit != (*it).end(); ++sit) {
            printf("%d", *sit);
            sumw += w[*sit - 1];                           //w数组下标从0开始
            sumv += v[*sit - 1];                           //v数组下标从0开始
        }
        printf("}\t\t%d\t%d\t", sumw, sumv);
        if (sumw <= W) {
            printf("能\n");
            if (sumv > best_sumv) {                        //比较求最优方案
                best_i = count;
                best_sumw = sumw;
                best_sumv = sumv;
            }
        }
        else printf("否\n");
        count++;               //方案编号+1
    }
    printf("0/1背包问题,最佳方案为:");
    printf("选择物品{ ");
        for (sit = ps[best_i].begin(); sit != ps[best_i].end(); ++sit) {
            printf("%d ", *sit);
        }
    printf("},");
    printf("总重量:%d,总价值:%d\n", best_sumw, best_sumv);
}
int main() {
    int n = 4, W = 6;
    int w[] = { 5,3,2,1 };
    int v[] = { 4,4,3,1 };
    PSet(n);    //求1~n的幂集ps
    Search(w, v, W);    //求所有方案和最佳方案
    return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

蛮力法求解0/1背包问题的时间复杂度为:O(2^n)

二、回溯算法

1)实现代码

#include<stdio.h>
#include<iostream>
using namespace std;
#define MAXN 30	//最多物品数
//问题表示
int n, W;	//n个数,W容量
double w[MAXN], v[MAXN];	//物品重量和价值
int x[MAXN];	//存放最终解
int bestp;		//存放最优解的总价值

//求解0/1背包问题
void Backtrack(int t, int tw, int tv, int op[]) 
{
	if (t > n) {		//找到一个叶子结点
		if (tw <= W && tv > bestp) {		//找到一个满足条件的更优解,保存它
			bestp = tv;
			for (int j = 1; j <= n; j++)
				x[j] = op[j];
		}
	}
	else {			//还没搜寻玩所有物品
		if (tw + w[t] <= W) {		//左孩子结点剪枝:满足条件才放入第i个物品
			op[t] = 1;		//选取第i个物品
			Backtrack(t+1,tw+w[t],tv+v[t],op);
		}
		op[t] = 0;		//不选取第i个物品,回溯
		Backtrack(t + 1, tw, tv, op);
	}
}
void disp_bestx() {
	int i;
	int sumw = 0;
	cout << "放入购物车的物品序号为:";
	for (i = 1; i <= n; i++) {
		if (x[i] == 1) {
			cout << i << " ";
			sumw += w[i];
		}
	}
	cout << endl;
	cout << "放入购物车的物品最大价值为:" << bestp << ",总重量为:" << W << endl;
}
int main() {
	cout << "输入物品个数n:"; cin >> n;
	cout << "输入购物车容量W:"; cin >> W;
	cout << "依次输入每个物品的重量w和价值v,用空格分开:";
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> v[i];
	}
	int op[MAXN];		//存放临时解
	Backtrack(1, 0, 0, op);
	disp_bestx();
	return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

最不理想的情况下,回溯法求解0/1背包问题的时间复杂度为:O(2^n)
由于其对蛮力法进行优化后的算法,其复杂度一般比蛮力法要小。
空间复杂度:有n个物品,即最多递归n层,存储物品信息就是一个一维数组,即回溯法求解0/1背包问题的空间复杂度为:n。

三、分支限界算法

1)实现代码

#include <iostream>
#define N 30
using namespace std;
int n;double W; //n个数,W容量
double w[N];double v[N];  //物品重量和价值
bool x[N];  
bool best_x[N]; //存储最优方案
double now_v;   //当前价值
double remain_v;    //剩余价值
double now_w;   //当前容量
double best_v;  //最优价值
double Bound(int k)     //计算分枝结点k的上界
{
    remain_v = 0;
    while (k <= n) {
        remain_v += v[k];
        k++;
    }
    return remain_v + now_v;
}
void Backtrack(int t)
{
    if (t > n) {  //是否到达叶节点
        for (int i = 1; i <= n; i++) {
            best_x[i] = x[i];   //记录回溯的最优情况
        }
        best_v = now_v; //记录回溯中的最优价值
        return;
    }
    if (now_w + w[t] <= W) {  //约束条件,是否放入。放入考虑左子树,否则考虑右子树
        x[t] = 1;
        now_w += w[t];
        now_v += v[t];
        Backtrack(t + 1); //进行下一个节点的分析
        now_w -= w[t];  //在到达叶节点后进行回溯
        now_v -= v[t];
    }
    if (Bound(t + 1) > best_v) {    //限界条件,是否剪枝。若放入t后不满足约束条件则进行到此处,然后判断若当前价值加剩余价值都达不到最优,则没必要进行下去
        x[t] = 0;
        Backtrack(t + 1);
    }
}
void Knapsack(double W, int n)
{
    double sum_w = 0;
    double sum_v = 0;
    best_v = 0;
    for (int i = 0; i < n; i++) {
        sum_w += w[i];
        sum_v += v[i];
    }
    Backtrack(1);
    cout << "放入购物车的物品最大价值为:" << best_v << endl;
    cout << "放入购物车的物品序号为:" << endl;
    for (int i = 1; i <= n; i++) {
        if(x[i] == 1)
            cout << i << " ";
    }
}
int main()
{
    cout << "输入物品个数n:"; cin >> n;
    cout << "输入购物车容量W:"; cin >> W;
    cout << "依次输入每个物品的重量w和价值v,用空格分开:\n";
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    Knapsack(W, n);
    return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

分支限界法求解0/1背包问题的时间复杂度为:O(2^n)

四、贪心算法

1)实现代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAXN 51
int n;
int W;
struct NodeType
{
	int w;
	int v;
	int p;				//性价比p=v/w
	bool operator<(const NodeType& s)const
	{
		return p > s.p;
	}
};
NodeType A[MAXN];
int maxv;
int x[MAXN];
void Knap()						//求解背包问题并返回总价值
{
	maxv = 0;					//maxv初始化为0
	int weight = W;				//背包中能装入的余下重量	
	memset(x, 0, sizeof(x));	//初始化x向量
	int i = 1;
	while (A[i].w <= weight)		//物品i能够全部装入背包时,循环
	{
		x[i] = 1;				//装入物品i
		weight -= A[i].w;		//减少背包中能装入的余下重量
		maxv += A[i].v;			//计算装入物品i后的总价值
		i++;
	}
}
void disp_bestx() {
	int sumw = 0;
	cout << "放入购物车的物品序号为:";
	for (int j = 1; j <= n; j++) {
		if (x[j] == 1) {
			cout << j << " ";
			sumw += A[j].w;
		}
	}
	cout << endl;
	cout << "放入购物车的物品最大价值为:" << maxv << ",总重量为:" << sumw << endl;
}
int main()
{
	cout << "输入物品个数n:"; cin >> n;
	cout << "输入购物车容量W:"; cin >> W;
	cout << "依次输入每个物品的重量w和价值v,用空格分开:" << endl;;
	for (int i = 1; i <= n; i++) {
		cin >> A[i].w >> A[i].v;
	}
	for (int i = 1; i <= n; i++) {
		A[i].p = A[i].v / A[i].w;
	}
	sort(A + 1, A + 1 + n);
	Knap();
	disp_bestx();
	return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

贪心法求解0/1背包问题的时间复杂度为:O( nlog(n) )

五、非递归-动态规划

1)实现代码

#include<stdio.h>
#include<iostream>
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
#define MAXN 30		//最多物品数
#define MAXW 100	//最大限制重量
//问题表示
int n, W;	//n个数,W容量
int w[MAXN], v[MAXN];	//物品重量和价值
//求解结果表示
int dp[MAXN][MAXW];
int x[MAXN];
int bestp;		//存放最优解的总价值
//用动态规划法求0/1背包问题
void Knap()
{
	int i, r;
	for (i = 0; i <= n; i++)	//置边界条件dp[i][0] = 0
		dp[i][0] = 0;
	for (r = 0; r <= W; r++)	//置边界条件dp[0][r] = 0
		dp[0][r] = 0;
	for (i = 1; i <= n; i++) {
		for (r = 1; r <= W; r++) {
			if (r < w[i])
				dp[i][r] = dp[i - 1][r];
			else
				dp[i][r] = max(dp[i - 1][r], dp[i - 1][r - w[i]] + v[i]);
		}
	}
}
void Buildx()		//回推求最优解
{
	int i = n, r = W;
	bestp = 0;
	while (i >= 0) {
		if (dp[i][r] != dp[i - 1][r]) {
			x[i] = 1;
			bestp += v[i];
			r = r - w[i];
		}
		else
			x[i] = 0;
		i--;
	}
}
int main() {
	cout << "输入物品个数n:"; cin >> n;
	cout << "输入最大容量W:"; cin >> W;
	cout << "依次输入每个物品的重量w和价值v,用空格分开:";
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> v[i];
	}
	Knap();
	Buildx();
	printf("最优方案\n");
	printf("选取物品为:");
	for (int i = 1; i <= n; i++)
		if (x[i] == 1)
			printf("%d ", i);
	printf("\n");
	printf("总价值=%d\n", bestp);
	return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

动态规划法求解0/1背包问题的时间复杂度为:O(nW)
空间复杂度为:O(nW)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值