背包问题求解
拟解决生活中常见的问题之一:背包问题
该问题要求在一个物品集合中选择合适的物品放入背包,在放入背包中的物品总重量不超过背包容量的前提下,希望放入背包的物品总价值最大。根
据是否允许部分物品放入背包的要求,背包问题可以分为分数背包问
题和 0-1 背包问题。
对于分数背包问题,可以通过设计贪心算法得到问题实例的最优
解。对于 0-1 背包问题,该问题已经被证明为 NP-Hard,即不存在多
项式时间算法那求解,但可以通过贪心算法得到问题的近似解,或者
通过蛮力法、动态规划法得到问题的最优解。能分析各个算法所
使用的算法设计技术和时间复杂度。下列基本要求必须完成:
1、设计一个交互界面(例如菜单)供用户选择,如果可能,最好是
一个图形化用户界面;
2、能够人工输入一个背包问题具体实例,涉及物品个数、每个物品
的重量和价值,以及背包容量;
3、设计一个贪心算法求解分数背包问题给定实例的最优解,并分析
算法的时间复杂度;
4、设计一个贪心算法求解 0-1 背包问题给定实例的近似解,请提供
一个反例判断该算法不能总是能够给出最优解,并证明算法的解
和最优解的值的比值大于等于 1/2。
5、设计一个蛮力法算法求解 0-1 背包问题给定实例的最优解,并分
析算法的时间复杂度;
6、设计一个动态规划算法求解求解 0-1 背包问题给定实例的最优解,
并分析算法的时间复杂度;
wxml
<view class="box1">
<text>请输入数量:</text>
<input bindinput="numberInput"/>
<text>请输入重量:</text>
<input bindinput="weightInput"/>
<text>请输入价值:</text>
<input bindinput="valueInput"/>
<text>请输入背包重量:</text>
<input bindinput="maxWeightInput"/>
</view>
<view class="box4">
<view>贪心法求分数背包</view>
<text>{{total}}</text>
<button type="primary" size="mini" class="btn2" bindtap="callTanxin_score">按我</button>
</view>
<view class="box4">
<view>动态规划求01背包</view>
<text>{{total1}}</text>
<button type="primary" size="mini" class="btn2" bindtap="callKnapscak">按我</button>
</view>
<view class="box4">
<view>穷举法求01背包</view>
<text>{{total2}}</text>
<button type="primary" size="mini" class="btn2" bindtap="callBruteForce">按我</button>
</view>
<view class="box4">
<view>贪心法求01背包</view>
<text>价值:{{total3}}</text>
<text>重量:{{total3weight}}</text>
<button type="primary" size="mini" class="btn2" bindtap="callTanxin_01">按我</button>
</view>
wxss
/* pages/home/home.wxss */
page{
background: #eeeeee;
}
.show_array{
min-height: 80rpx;
width: 720rpx;
line-height: 40rpx;
word-wrap: break-word;
word-break: break-all;
padding: 12rpx 0;
letter-spacing: 2rpx;
}
.box1{
width: 704rpx;
padding-left: 40rpx;
padding-top: 10rpx;
border-radius: 12rpx;
background: #ffffff;
margin: 14rpx auto;
padding-bottom: 20rpx;
font-size: 30rpx;
line-height: 40rpx;
color: #141414;
}
.box4{
height: 130rpx;
width: 704rpx;
padding-left: 40rpx;
padding-top: 10rpx;
border-radius: 12rpx;
background: #ffffff;
margin: 14rpx auto;
/* padding-bottom: 20rpx; */
font-size: 30rpx;
line-height: 40rpx;
color: #141414;
}
.btn2{
position: absolute;
right: 30rpx;
}
.box4 text{
display: inline-block;
margin-right:40rpx;
}
js
Page({
/**
* 页面的初始数据
*/
data: {
number:0,
weight:[],
value:[],
maxWeight:0,
selected : [],
F:[],
total:'',//贪心分数
total1:'',//动态规划
total2:'',//穷举
total3:'',//贪心01
total3weight:'',//贪心01总重量
},
numberInput(e){
let testarray=e.detail.value
this.data.number=parseInt(testarray)
},
weightInput(e){
let testarray=e.detail.value
// this.data.weight=testarray.split(",")
let nums=testarray.split(",")
var turnNum=function(){
for(let i=0;i<nums.length;i++){
nums[i]=parseInt(nums[i])
}
return nums
}
this.data.weight=turnNum()
},
valueInput(e){
let testarray=e.detail.value
// this.data.value=testarray.split(",")
let nums=testarray.split(",")
var turnNum=function(){
for(let i=0;i<nums.length;i++){
nums[i]=parseInt(nums[i])
}
return nums
}
this.data.value=turnNum()
},
maxWeightInput(e){
let testarray=e.detail.value
this.data.maxWeight=testarray
},
// 贪心算法求分数背包
//贪心算法,只能算,可以分割的物品,如果不能分割物品,只能得到近似解,不分割物品,可以使用动态规划
//1、计算每件商品的(价格/质量),即单位质量的价值
//2、将单位质量价值排序
tanxin_score:function (){
// let weights=[2,2,6,5,4]
// let values=[6,3,5,4,6]
// let capacity=10
let weights=this.data.weight
let values=this.data.value
let capacity=this.data.maxWeight
var list = [];
for(var i = 0,len = weights.length; i < len; i++){
list.push({
num:i+1, //第几件商品
w:weights[i], //重量
v:values[i],
rate:values[i]/weights[i]
});
}
list.sort(function(a,b){
if(a.rate > b.rate){
return -1;
}else{
return 1;
}
});
var selects = [];
var total = 0;
for(var i = 0,len = list.length; i < len; i++){
var item = list[i];
if(item['w'] <= capacity){
selects.push({
num:item.num,
rate:1 , //完整的商品记录为1
v:item.v,
w:item.w
});
total = total + item.v;
capacity = capacity - item.w;
}else if(capacity > 0){
//选取不完整的商品
var rate = capacity/item['w'];
var v = item.v*rate;
selects.push({
num:item.num,
rate: rate,
v:item.v*rate,
w:item.w*rate
});
total = total + v;
break;
}else{
break;
}
}
return {
selects,
total
}
},
callTanxin_score:function(){
console.log(this.tanxin_score())
this.setData({
total:this.tanxin_score().total
})
},
// 动态规划
knapsack:function (){
let weights=this.data.weight
let values=this.data.value
let W=this.data.maxWeight
var n = weights.length -1
var f = [[]] //记录结果矩阵
for(var j = 0; j <= W; j++){
if(j < weights[0]){ //如果容量不能放下物品0的重量,那么价值为0
f[0][j] = 0
}else{ //否则等于物体0的价值
f[0][j] = values[0]
}
}
for(var j = 0; j <= W; j++){
for(var i = 1; i <= n; i++ ){
if(!f[i]){ //创建新一行
f[i] = []
}
if(j < weights[i]){ //等于之前的最优值
f[i][j] = f[i-1][j]
}else{
f[i][j] = Math.max(f[i-1][j], f[i-1][j-weights[i]] + values[i])
}
}
}
console.log(f.concat())
return f[n][W]
},
callKnapscak:function(){
// this.knapsack()
this.setData({
total1:this.knapsack()
})
},
// 递归
BFknapsack:function (n, W, weights, values, selected) {
if (n == 0 || W == 0) {
//当物品数量为0,或者背包容量为0时,最优解为0
return 0;
} else {
//从当前所剩物品的最后一个物品开始向前,逐个判断是否要添加到背包中
for (var i = n - 1; i >= 0; i--) {
//如果当前要判断的物品重量大于背包当前所剩的容量,那么就不选择这个物品
//在这种情况的最优解为f(n-1,C)
if (weights[i] > W) {
return this.BFknapsack(n - 1, W, weights, values, selected);
} else {
var a = this.BFknapsack(n - 1, W, weights, values, selected); //不选择物品i的情况下的最优解
var b = values[i] + this.BFknapsack(n - 1, W - weights[i], weights, values, selected); //选择物品i的情况下的最优解
//返回选择物品i和不选择物品i中最优解大的一个
if (a > b) {
selected[i] = 0; //这种情况下表示物品i未被选取
return a;
} else {
selected[i] = 1; //物品i被选取
return b;
}
}
}
}
},
callBFknapsack:function(){
let selected=this.data.selected
let ws=[2,2,6,5,4]
let vs=[6,3,5,4,6]
console.log(this.BFknapsack(5,10,ws,vs,selected))
selected.forEach(function(el,i){
if(el){
console.log("选择了物品"+i+ " 其重量为"+ ws[i]+" 其价值为"+vs[i])
}
})
},
// 背包问题记忆化
MFKnapsack:function(i,j,F){
var value = 0;
// let F=this.data.F
if(F[i][j] < 0){
if (j < parseInt(this.pws[i])){
value = this.MFKnapsack(i-1,j,F)
}else{
value = this.max(this.MFKnapsack(i-1,j,F),parseInt(this.pvs[i])+this.MFKnapsack(i-1,j-parseInt(this.pws[i]),F))
}
this.F[i][j] = value;
}
return this.F[i][j],F;
},
//求最大值
max:function(a,b){
if(a>b){
return a;
}else{
return b;
}
},
//背包问题主函数
backpackmain:function(){
// let pws=[2,2,6,5,4]
// let pvs=[6,3,5,4,6]
let pws=this.data.weight
let pvs=this.data.value
let F=this.data.F
let pweight=''
let pvalue=''
let backpackweight=''
let ts=[]
//pws和pvs首端添加0,因为背包的记忆功能算法使用的是Weights[1..n]和Valuess[1..n]
pws=pws.splice(0,0,0);
pvs=pvs.splice(0,0,0);
F = new Array(pws.length);
//初始化值
for (var i = 0; i < pws.length; i++) {
F[i] = new Array(parseInt(backpackweight)+1)
for (var j = 0; j <= parseInt(backpackweight); j++) {
if(i==0 || j==0){
F[i][j] = 0;
}else{
F[i][j] = -1;
}
}
}
this.MFKnapsack(pws.length -1,parseInt(backpackweight),F)
//输出最优子集
things = new Array();
var j = parseInt(backpackweight);
for (var i = pws.length-1; i > 0; i--) {
if(F[i][j] == F[i-1][j]){
while(F[i][j] == F[i-1][j]){
i--;
}
}
ts.push(i)
j = j - pws[i];
}
console.log(F)
console.log(ts)
},
// 穷举法
bruteForce:function(){
// let weights=[7,3,4,5]
// let values=[42,12,40,25]
// let W=10
// let n=4
let weights=this.data.weight
let values=this.data.value
let W=this.data.maxWeight
let n=this.data.number
let height= Math.pow(2,n)
var arr =Array(height).fill().map(() => Array(n))
for(var i =0;i <height;i++){ //总共有2^n个子集,需要进行2^n次循环,及数组A有2^n行
var temp1 = i;
for(var j =0;j < n;j++){ //数组A有n列,每一列代表一个物品
var temp2 = parseInt(temp1 % 2);
arr[i][j] = temp2;
temp1 =parseInt(temp1 / 2);
}
}
this.printArray(arr,weights,values,W);
let maxSumValue=this.printArray(arr,weights,values,W)
return maxSumValue
},
//输出穷举方案的背包实例的选择物品(0代表不包含该物品,1表示包含该物品)的总重量及总价值,并输出最优实例的总价值
printArray:function(arr,weight,value,maxWeight){
// let arr = new Array(16).fill(new Array(4).fill(0))
let len1 = arr.length //二维数组的行数
let len2 = arr[0].length //二维数组的列数
let maxSumValue=0
for(let i=0;i<len1;i++){
let tempWeight = 0; //暂时计算当前选中背包实例物品的总重量,初始化为0
let tempSumValue = 0; //暂时计算当前选中背包实例物品的总价值,初始化为0
let result=" "
for(let j = 0;j < len2;j++){
result+=arr[i][j]+" "
tempWeight += arr[i][j]*weight[j]
tempSumValue += arr[i][j]*value[j]
}
console.log(result)
console.log("\t"+"总重量为:"+tempWeight);
if(tempWeight <= maxWeight){
console.log("\t"+"总价值为:"+tempSumValue)
}
else{
console.log("\t"+"不可行(超出背包最大承重)")
}
if(tempWeight <= maxWeight && tempSumValue > maxSumValue)
maxSumValue = tempSumValue;
console.log();
}
console.log("穷举查找得知,最优解的总价值为:"+maxSumValue);
return maxSumValue
},
callBruteForce:function(){
this.bruteForce()
this.setData({
total2:this.bruteForce()
})
},
//贪心法求01背包
//求价值
tanxin_01:function() {
// let weights=[35, 30, 60, 50, 40, 10, 25]
// let values=[10, 40, 30, 50, 35, 40, 30]
// let W=150
let weights=this.data.weight
let values=this.data.value
let W=this.data.maxWeight
let n=this.data.number
//存储累加的重量和价值
let maxZhong=0;
let maxJia=0;
//存放密度
let midu=new Array(n)
//存放索引的数组
let index=new Array(n)
for (let i = 0; i < midu.length; i++)
{
midu[i]=parseFloat(values[i]/weights[i]);
index[i]=i;
}
//冒泡排序
for (let i = 0; i < midu.length-1; i++)
{
for (let j = 0; j < midu.length-1-i; j++)
{
if (midu[j]<midu[j+1])
{
let temp =parseFloat(midu[j]);
midu[j]=midu[j+1];
midu[j+1]=temp;
//索引跟着交换
let temp1=index[j];
index[j]=index[j+1];
index[j+1]=temp1;
}
}
}
//计算最优解
for (let i = 0; i < index.length; i++)
{
if (weights[index[i]]<=W)
{
maxZhong+=weights[index[i]];
maxJia+=values[index[i]];
W-=weights[index[i]];
}
}
console.log("总重量:"+maxZhong);
console.log("总价值:"+maxJia);
return {maxZhong,maxJia}
},
callTanxin_01:function(){
this.tanxin_01()
this.setData({
total3:this.tanxin_01().maxJia,
total3weight:this.tanxin_01().maxZhong
})
},
})