01背包跳跃点优化
经典算法的缺点
- 无法处理物品价值、体积为小数的情况
- 当背包容量非常大时,算法的时间复杂度很高
改进的思路(跳跃点)
通过观察经典算法构建的二维表,发现只有部分“跳跃点”会对结果产生影响,其余点并不能更新最大值。如果只处理这些“跳跃点”,那么就可以克服上面两个缺点。
具体方法
前
i
−
1
i-1
i−1个物品所构成的跳跃点
(
w
,
v
)
(w,v)
(w,v)是一个递增序列。那么加上第i个物品,相当于每个跳跃点都向右上方移动了一段距离,依然是一个递增序列。
记前
i
−
1
i-1
i−1个物品的跳跃点集合为为
P
P
P,整体平移后的跳跃点集合为
Q
Q
Q。那么,新的跳跃点集合就是
P
∪
Q
P\cup Q
P∪Q。不过,其中有一部分的跳跃点是无意义的,例如:新的集合为
(
1
,
2
)
、
(
1
,
4
)
、
(
2
,
3
)
(1,2)、(1,4)、(2,3)
(1,2)、(1,4)、(2,3),跳跃点
(
1
,
4
)
(1,4)
(1,4)优于
(
1
,
2
)
、
(
2
,
3
)
(1,2)、(2,3)
(1,2)、(2,3),所以需要对集合
P
∪
Q
P\cup Q
P∪Q进行删减。
如果是一个乱序的集合,对无效跳跃点进行删减是无法在
O
(
n
)
O(n)
O(n)的复杂度内完成的。好在原先的集合
P
P
P与
Q
Q
Q都是递增的,那么我们可以采用类似归并排序中两个有序序列合并的方法,将
P
P
P与
Q
Q
Q在
O
(
n
)
O(n)
O(n)复杂度内合并为一个新的跳跃点集合。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1005;
const double eps=1e-6;
int n;
double vol,v[maxn],w[maxn];
vector<pair<double,double>> p[maxn],q;
int main(){
printf("-----01背包问题跳跃点改进-----\n");
printf("1. 支持物品重量,价值为小数\n");
printf("2. 支持大背包容量\n\n");
printf("输入物品数量,背包容量:");
scanf("%d%lf",&n,&vol);
printf("依次输入每个物品的价值:\n");
for(int i=1;i<=n;i++) scanf("%lf",&v[i]);
printf("依次输入每个物品的重量:\n");
for(int i=1;i<=n;i++) scanf("%lf",&w[i]);
p[0].push_back({0,0});
for(int i=1;i<=n;i++){
q.clear();
for(int j=0;j<p[i-1].size();j++){
double f=p[i-1][j].first,s=p[i-1][j].second;
if(f+w[i]<=vol) q.push_back({f+w[i],s+v[i]});
}
int a=p[i-1].size(),b=q.size(),c=0,d=0;
double maxm=-1;
while(c<a||d<b){
while(c<a&&p[i-1][c].second<=maxm) c++;
while(d<b&&q[d].second<=maxm) d++;
if(c<a&&d<b){
if(abs(p[i-1][c].first-q[d].first)<eps) p[i].push_back(max(p[i-1][c],q[d])),maxm=max(p[i-1][c++],q[d++]).second;
else if(p[i-1][c].first<q[d].first) p[i].push_back(p[i-1][c]),maxm=p[i-1][c++].second;
else p[i].push_back(q[d]),maxm=q[d++].second;
}else if(c==a){
while(d<b)
if(q[d].second>maxm) p[i].push_back(q[d]),maxm=q[d++].second;
else d++;
}else if(d==b){
while(c<a)
if(p[i-1][c].second>maxm) p[i].push_back(p[i-1][c]),maxm=p[i-1][c++].second;
else c++;
}
}
}
cout<<"背包最多装价值为"<<p[n].back().second<<"的物品\n这些物品是:";
double x=p[n].back().first,y=p[n].back().second;
int i=n; q.clear();
while(x>eps||y>eps){
pair<double,double> t=*lower_bound(p[i-1].begin(),p[i-1].end(),make_pair(x,y));
if(abs(t.first-x)<eps&&abs(t.second-y)<eps) i--;
else x-=w[i],y-=v[i],q.push_back({i--,0});
}
for(int i=q.size()-1;i>=0;i--) cout<<"物品"<<(int)q[i].first<<(i==0?'\n':' ');
}