动态规划01背包问题之跳跃点解法

01背包问题的跳跃点解法

解法分析

m ( i , j ) = m a x { m ( i − 1 , j ) , m ( i − 1 , j − w [ i ] ) + v [ i ] } m(i,j) = max\{m(i-1,j) , m(i-1,j-w[i])+v[i]\} mij=max{mi1jmi1jw[i]+v[i]}
m ( i , j ) 代 表 余 量 j 从 第 一 个 物 品 到 第 i 个 物 品 的 最 优 价 值 m(i,j)代表余量j从第一个物品到第i个物品的最优价值 m(i,j)ji
p ( i ) 代 表 m ( i , j ) 的 跳 跃 点 点 集 p(i)代表m(i,j)的跳跃点点集 p(i)m(i,j)
q ( i ) 代 表 m ( i , j − w [ i ] ) + v [ i ] 的 跳 跃 点 点 集 合 q(i)代表 m(i,j-w[i])+v[i]的跳跃点点集合 q(i)mijw[i]+v[i]

备注
1. 其 中 其中 q(i) 可 由 p ( i ) 的 每 一 个 点 加 上 ( w [ i ] , v [ i ] ) 得 到 。 可由p(i)的每一个点加上(w[i],v[i])得到。 p(i)(w[i],v[i])
2. p ( i − 1 ) 并 上 q ( i − 1 ) 然 后 减 去 必 然 不 合 法 的 点 ( 同 w 下 v 非 最 大 的 点 和 w 较 大 但 是 v 较 小 ) 即 为 p ( i ) p(i-1)并上 q(i-1)然后减去必然不合法的点(同 w 下 v 非最大的点和w较大但是v较小)即为 p(i) p(i1q(i1)wvwv)pi

代码实现

具体代码实现的注意事项:
使用一维表table[]加head[]来表示二维表
head[i]:表示第i个行的开端。
其中在pass不合法的点对时,对于q和p中的值采用的方法不一致。具体看代码注释

 const int N = 1e4, M = 1e6;
  
  struct Data
  {
  	int w, v;
  	Data(int nw = 0, int nv = 0) :w(nw), v(nv) {}//构造函数,默认w,v都为0
  	Data operator +(const Data& r)const//设计data结构体的+操作
  	{
  		return Data(w + r.w, v + r.v);
  	}
  
  	bool operator ==(const Data& r)const//设计data结构体的==操作
  	{
  		return (w == r.w) && (v == r.v);
  	}
  };
  
  int head[N + 2];//标记每一个i的表的首元素,相当于将一维数组转换为二维数组
  Data goods[N], table[M];//goods[]存储物品信息,table[i]存储对应装前i个物品对应的(w,v)
  stack<int> numlist;//存放最终放入物品序号的栈
  int n, c;//物品数量和背包总容量
  
  // trace back to find the solution vector x[1……n]
  void traceBack(Data eState)
  {//eState是最后一个i的表的最后一个数据信息
  	int i, j;
  	bool x[N + 2];
  	for (i = n; i >= 1; --i)
  	{
  		x[i] = false;
  		for (j = head[i] - 1; j >= head[i - 1]; --j)//从p[i-1]的末尾遍历到p[i-1]的开头
  		{
  			if (table[j] + goods[i] == eState && (table[j].w != 0 || !j))
  			{//这里判断有没有加入第i个物品,若加入,则将i序号进栈,并且将对应i-1的表的最好的数据赋值给estate进行回溯
  				numlist.push(i);
  				//cout << i << ",";//测试进栈情况
  				eState = table[j];
  				break;
  			}
  		}
  	}
  }
  
  // jump points' method to slove the 0-1 bag's problem
  int GKnapSack()
  {
  	int i, k, j, boundaryL, boundaryR, next;//boundaryL, boundaryR作为指针,卡在p[i-1]的左右为边界,next是p[i]要填写的位置,初始时next为
  	//p[i]的首位置,k依次往后移动,直到到达p[i-1]的右边界
  	Data temp;
  
  	head[0] = 0;//p[0]的首位置为索引0
  	table[0] = Data(0, 0);//没有装物品时各项数据都为0
  	boundaryL = boundaryR = 0;
  	next = 1;//p[1]要填写的位置即索引1
  	head[1] = 1;//p[i]的首位置为1
  	for (i = 1; i <= n; ++i)
  	{
  		k = boundaryL;//数字k在处理中从p[i-1]的左边界移动到右边界
  		for (j = boundaryL; j <= boundaryR; ++j)
  		{
  			if (table[j].w + goods[i].w > c)
  				break;
  			/*先在 p(i-1)的元素 j 上得到一个新状态,然后 w 小于它的不受影响,直接搬*/
  			temp = table[j] + goods[i];//将p[i-1]对应的列表中的移动到的数据的容量和价值分别加上第i件物品的容量和价值
  			while (k <= boundaryR && table[k].w<temp.w)  
  			{//next是p[i]要填写的位置,初始时next为p[i]的首位置,依次往后移动,
  	         //直到到达p[i-1]的右边界
  				table[next] = table[k];
  			++next;
  			++k;
  			}
  			/*w 等于它的,对 v 取更大的值*/
  				if (k <= boundaryR && table[k].w == temp.w)
  				{
  					temp.v = max(temp.v, table[k].v);//如果容量相同,则取价值最大的
  					++k;
  				}
  			/*w 大于它的,根据 v 值直接pass 掉 不合法的点(w 大但 v 小于前边的)*/  //这里pass的为q队列不合适的对
  			if (temp.v > table[next - 1].v)
  			{
  				table[next] = temp;
  				++next;
  			}
  			while (k <= boundaryR && table[k].v <= table[next - 1].v)
  				++k;                                                       //将多余的删除掉。这里忽略在p行的违规的对
  		}
  		while (k <= boundaryR)
  		{
  			table[next] = table[k];
 			++next;
 			++k;
 		}
 
 		boundaryL = boundaryR + 1;//boundaryL指向p[i]的左边界,即下一个i对应的表的左边界,因为此时p[i]的表已经填好,要利用i标记来借助p[i]导出p[i+1]
 		boundaryR = next - 1;//因为next指向p[i+1]的首位置的索引,所以boundaryR指向next-1,即得到p[i]的右边界
 		head[i + 1] = next;
 	}
 
 	traceBack(table[next - 1]);
 
 	return table[next - 1].v;
 }

算法复杂度分析?:
此算法的计算量在于求解p[i]
p[i]的跳跃点对应 x 1 , . . . , x i x_1,...,x_i x1,...,xi的0-1赋值,p[i]的跳跃点个数不超过 2 i 2^i 2i故计算时间为
O ( ∑ i = 1 n 2 n = O ( 2 n ) O(\sum^n_{i=1}{2^n}=O(2^n) O(i=1n2n=O(2n)
当所给的物品重量为整数时, ∣ p [ i ] ∣ < = c + 1 |p[i]|<=c+1 p[i]<=c+1,所以总的时间复杂度为 O ( m i n ( n c , 2 n ) ) O(min(nc,2^n)) O(min(nc,2n))

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值