背包问题
一堆物品,每件物品有对应的重量和价值。有一个固定容量的背包,随便装物品,如何让背包装的东西价值最大?
问题分析
背包中物品价值最大时,可能存在以下几种情况:
- 背包正好装了一件足够大的物品,物品价值比分开装其他物品的总价值还高;
- 背包空间可以分成A和B两部分,空间A和B都最大化利用,装价值最大的物品。但一种拆分方案没法保证合理拆分,必须遍历多种拆分方案;
因此,背包为题等效为对比情况1和情况2中最大的价值;情况2则可以拆分成两个背包问题的子问题,递归的情况下,可以继续不断拆分,直到无法继续拆分为止。问题拆分求解过程中,有些问题必然会重复出现。为了避免重复求解,还需要对已经求解过的问题的答案进行记录。
流程图
挑选物品流程图如下,粉丝方框表示迭代。
代码实现
用c#来实现,主要代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PackageProblem
{
public class DynamicProgramming
{
public List<Goods> GoodsForChose { get; set; }
public int TotalWeightLimit { get; set; }
public List<Goods> GoodsSelectd { get; set; }
public int GoodsTotalWeight { get; set; }
public int GoodsTotalValue { get; set; }
private List<Tuple<int, List<int>, List<int>>> _tuplesRecord;
public DynamicProgramming(int totalWeightLimit, List<Goods> goodsForSelect)
{
TotalWeightLimit = totalWeightLimit;
GoodsForChose = goodsForSelect;
_tuplesRecord = new List<Tuple<int, List<int>, List<int>>>();
}
public void SelectGoods()
{
var toSelectList = SelectGoods(TotalWeightLimit, GoodsForChose.Select((x, i) => i).ToList());
GoodsSelectd = toSelectList.Select(x => GoodsForChose[x]).ToList();
GoodsTotalWeight = GoodsSelectd.Select(x => x.Weight).Sum();
GoodsTotalValue = GoodsSelectd.Select(x => x.Value).Sum();
}
private List<int> SelectGoods(int totalWeightLimit, List<int> goodsIndexForSelect)
{
var exitTuple = _tuplesRecord.Where(x => x.Item1 == totalWeightLimit && ListIntEqual(x.Item2, goodsIndexForSelect)).FirstOrDefault();
if (exitTuple != null) return exitTuple.Item3;
if (totalWeightLimit < goodsIndexForSelect.Select(x => GoodsForChose[x].Weight).Min()) return new List<int>();
var oneGoods = SelectOneGoods(totalWeightLimit, goodsIndexForSelect);
//if (oneGoods < 0) return new List<int>();
var totalValue = GoodsForChose[oneGoods].Value;
var totalGroup = new List<int> { oneGoods };
for (var i = 1; i <= totalWeightLimit / 2; i++)
{
var partA = SelectGoods(i, goodsIndexForSelect);
var tempTuple1 = new Tuple<int, List<int>, List<int>>(i, goodsIndexForSelect, partA);
_tuplesRecord = AddTupleAndDistinct(_tuplesRecord, tempTuple1);
var partB = SelectGoods(totalWeightLimit - i, goodsIndexForSelect.Except(partA).ToList());
var tempTuple2 = new Tuple<int, List<int>, List<int>>(i, goodsIndexForSelect, partA);
_tuplesRecord = AddTupleAndDistinct(_tuplesRecord, tempTuple2);
var tempGroup = partA.ToList();
tempGroup.AddRange(partB);
var tempTotalValue = tempGroup.Select(x => GoodsForChose[x].Value).Sum();
if (tempTotalValue > totalValue) { totalValue = tempTotalValue; totalGroup = tempGroup; }
}
_tuplesRecord = AddTupleAndDistinct(_tuplesRecord, new Tuple<int, List<int>, List<int>>(totalWeightLimit, goodsIndexForSelect, totalGroup));
return totalGroup;
}
private bool ListIntEqual(List<int> listA, List<int> listB)
{
var result = true;
if (listA.Count != listB.Count) return false;
listA.ForEach(x => result = result && listB.Contains(x));
return result;
}
private List<Tuple<int, List<int>, List<int>>> AddTupleAndDistinct(List<Tuple<int, List<int>, List<int>>> listTuple, Tuple<int, List<int>, List<int>> tuple)
{
var exitSameTuple = listTuple.Where(x => x.Item1 == tuple.Item1 && ListIntEqual(x.Item2, tuple.Item2)).Count() > 0;
if (!exitSameTuple) listTuple.Add(tuple);
return listTuple;
}
private int SelectOneGoods(int totalWeightLimit, List<int> goodsIndexForSelect)
{
var targetGoods = goodsIndexForSelect.Where(x => GoodsForChose[x].Weight <= totalWeightLimit);
if (targetGoods.Count() == 0) return -1;
return targetGoods.FirstOrDefault(x => GoodsForChose[x].Value == targetGoods.Select(y => GoodsForChose[y].Value).Max());
}
}
public class Goods
{
public string Name { get; set; }
public int Weight { get; set; }
public int Value { get; set; }
}
}
实现效果
为了调用上面的方法,做了个界面并准备相关数据,运行结果如下: