解法分析
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]\}
m(i,j)=max{m(i−1,j),m(i−1,j−w[i])+v[i]}
m
(
i
,
j
)
代
表
余
量
j
从
第
一
个
物
品
到
第
i
个
物
品
的
最
优
价
值
m(i,j)代表余量j从第一个物品到第i个物品的最优价值
m(i,j)代表余量j从第一个物品到第i个物品的最优价值
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)代表m(i,j−w[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(i−1)并上q(i−1)然后减去必然不合法的点(同w下v非最大的点和w较大但是v较小)即为p(i)
代码实现
具体代码实现的注意事项:
使用一维表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))