0-1背包问题的动态规划法与回溯法

一、动态规划

状态转移方程:

从前往后:
if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

从后往前:
if(j>=w[i])
    m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
else
    m[i][j]=m[i+1][j];

 算法:

从前往后:
for(int i=1;i<=n;i++)
	for(int j=1;j<=c;j++)
	{
		if(j>=w[i])
		{
			m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
		}
		else//这里没有考虑j<0的情况,因为算法中j取不到
		{
			m[i][j]=m[i-1][j];
		}
    }

从后往前:
for(int i=n;i>=1;i--)
	for(int j=1;j<=c;j++)
	{
		if(j>=w[i])
		{
			m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
		}
		else
		{
			m[i][j]=m[i+1][j];
		}
	}

例子:

例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制

重量数组w = {4, 6, 2, 2, 5, 1},

价值数组v = {8, 10, 6, 3, 7, 2},

背包容量C = 12时对应的m[i][j]数组。(从前往后)

0123456789101112
1000888888888
20008810101010181818
30668814141616181824
40669914141717191924
50669914141717192124
626891114161719192124

例题代码 :

#include<iostream>
#include<cmath>
#include<cstring>
#define N 20
using namespace std;
int main()
{
	int w[N]={0,4,6,2,2,5,1},v[N]={0,8,10,6,3,7,2};
	int m[N][N];
	memset(m,0,sizeof(m));
	int n=6,c=12;   //n,c均要小于N 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=c;j++)
	{
		if(j>=w[i])
		{
			m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
		}
		else
		{
			m[i][j]=m[i-1][j];
		}
	}
    cout<<m[n][c]<<endl; //从前往后

    /*
	for(int i=n;i>=1;i--)
	for(int j=1;j<=c;j++)
	{
		if(j>=w[i])
		{
			m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
		}
		else
		{
			m[i][j]=m[i+1][j];
		}
	}
    cout<<m[1][c]<<endl;//从后往前

	*/
	return 0;
}

二、回溯法

 1进入左子树条件:cw+w[i]<=c   //cw为当前重量

 2进入右子树条件(减枝函数):cp+r>bestp   //cp为当前价值,bestp为当前最优价值,r为当前剩余物品价值总和。cp+r由函数     Bound计算。

 3需要先将物品按单位重量价值从大到小排序,按序进入左子树;进入右子树时,由函数Bound计算当前节点上界,只有其上界大于当前最优价值bestp时,才进入右子树,否则减去。

算法:

void Backtrack(int i)
{
	if(i>n)  //到达叶节点
	{
		bestp=cp; 
		return;
	}
	if(cw+w[i]<=c)  //进入左子树
	{
		cw+=w[i];
		cp+=v[i];
		Backtrack(i+1);
		cw-=w[i];
		cp-=v[i];
	}
	if(Bound(i+1)>bestp)  //进入右子树
	{
		Backtrack(i+1);
	}
} 

int Bound(int i)  //计算上界
{
	int cleft=c-cw;
	int b=cp;          
	while(i<=n&&w[i]<=cleft)  //以物品单位重量价值递减序装入物品
	{
		cleft-=w[i];
		b+=v[i];
		i++;
	}
	if(i<=n)//装满背包
	{
		b+=v[i]*(cleft/w[i]);
	}
	return b;
}

例子代码:

#include<iostream>
#define N 20
using namespace std;
int w[N]={0,4,6,2,2,5,1},v[N]={0,8,10,6,3,7,2};
int n=6,c=12;
int cp=0,cw=0,bestp=0;
int Bound(int i)  //计算上界
{
	int cleft=c-cw;
	int b=cp;          
	while(i<=n&&w[i]<=cleft)  //以物品单位重量价值递减序装入物品
	{
		cleft-=w[i];
		b+=v[i];
		i++;
	}
	if(i<=n)//装满背包
	{
		b+=v[i]*(cleft/w[i]);
	}
	return b;
}
void Backtrack(int i)
{
	if(i>n)  //到达叶节点 
	{
		bestp=cp; 
		return;
	}
	if(cw+w[i]<=c)  //进入左子树
	{
		cw+=w[i];
		cp+=v[i];
		Backtrack(i+1);
		cw-=w[i];
		cp-=v[i];
	}
	if(Bound(i+1)>bestp)  //进入右子树 
	{
		Backtrack(i+1);
	}
} 

int main()
{
	Backtrack(1);
	cout<<bestp<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烜奕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值