以前感觉很高大上,现在理解了觉得也不是太难。其中0/1背包问题理解起来比较有难度。
代码参考课堂PPT完成,0/1背包问题的思考花费了大量时间。事实上,完全背包问题就是在容量x给定的基础上每次添加物品i,都会将所有物品遍历一遍,寻找当前应该加入的物品i,这个物品有可能重复;而0/1背包问题是在容量x给定的基础上,按照一定的规律,有序添加物品。即对j递增,判断第j个元素能否加入先前的某个已经求出的背包中。因为前面的某个已经求出的背包也是按照能否添加j的方式确定,因此就不会发生元素重复了。
总结一下
设K[x]为背包容量为x时的最大装配价值。
则有K[x] = Max{K[x],K[x - Wi] + Vi}, Wi<=x, 其中Wi是物品i的容量,Vi是物品i的价值。具体思路就是:
背包容量为x时的最大装配价值 = Max(之前的某个容量的最大装配价值 + 加入某个物品的装配价值)
为了确定“之前的某个容量的最大装配价值”,我们应该遍历物品序列,并且用x减去加入的第i个物品的容量,也就是说,x = x - Wi。举个例子,如果K[x] = K[x - W2] + V2,这就说明我们应该在当背包容量是x - W2的装配方案中装入新的物品V2,此时容量为x的背包有最大价值。
设K[x,j]表示背包容量为x时,装前j个元素(只用前j个元素)的最大装配价值。
在完全背包问题中,我们会考虑所有情况,不会去记忆之前装配的情况,不需要考虑物品的重复性,而在0/1背包问题中,每个物品只能装一次,因此,为了实现0/1背包问题,我们需要知道之前装过什么,之后就不能再装同一个物品了。 0/1背包问题的算法设计思路很清晰。我们把过程拆开来看,按照动态规划的子结构要求,在给定背包容量的情况下,我们要知道K[x,j]和K[x,j-1]的关系。
容易发现如果在K[x,j]的装配方案中,不对第j个物品予以考虑的话,有:
K[x,j] = K[x,j-1];
而如果要在K[x,j]的装配方案中对第j个物品予以考虑的话,有:
K[x,j] = Max{K[x,j],K[x - Wj,j - 1] + Vj},这里类似于完全背包问题,改改参数即可。
总的来说,每一次的判断即:是否装入第j个物品,如要装入,在之前的哪个位置装入第j个物品。如此一来就永远都不会发生重复添置物品的情况了。
using System;
using System.Collections.Generic;
using System.Text;
namespace Knapsack
{
public static class UnboundedKnapsack
{
/*演算:
* 设K[x]为背包容量为x时的最大装配价值,则有
*
*
* K[x] = Max{K[x],K[x - Wi] + Vi}, Wi<=x, 其中Wi是物品i的容量,Vi是物品i的价值。
* 具体思路就是:
* 背包容量为x时的最大装配价值 = Max(之前的某个容量的最大装配价值 + 加入某个物品的装配价值)
* 为了确定“之前的某个容量的最大装配价值”,我们应该用x减去加入的某个物品的容量,也就是说,
* x = x - Wi。举个例子,如果K[x] = K[x - W2] + V2,这就说明我们应该在当背包容量是x - W2的装配方案中
* 装入新的物品V2,此时容量为x的背包有最大价值。
*/
private static int[] K;
private static List<Item>[] PackSolutions;
public static int SolveUnboundedKnapsack(Item[] items, int capacity)
{
int n = items.Length;
PackSolutions = new List<Item>[capacity + 1];
K = new int[capacity + 1];
for(int i = 0; i < capacity + 1; i++)
{
K[i] = 0;
PackSolutions[i] = new List<Item>();
}
for (int x = 1; x <= capacity; x++)
{
K[x] = -1;
for(int i = 0; i < n; i++)
{
if(items[i].Weight <= x)
{
int q = K[x - items[i].Weight] + items[i].Value;
if(q > K[x])
{
K[x] = q;
PackSolutions[x].Clear();
PackSolutions[x - items[i].Weight].ForEach(item => PackSolutions[x].Add(item));
PackSolutions[x].Add(items[i]);
}
}
}
}
return K[capacity];
}
public static void PrintPackSolutions()
{
for(int i = 1; i < PackSolutions.Length; i++)
{
Console.WriteLine("当受重能力为" + i +"时:");
for(int j = 0; j < PackSolutions[i].Count; j++)
{
Console.WriteLine("选择" + PackSolutions[i][j].Name);
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Knapsack
{
public static class Zero_OneKnapsack
{
/*演算:
* 设K[x,j]表示背包容量为x时,装前j个元素(只用前j个元素)的最大配置价值
* 在完全背包问题中
* 不需要考虑物品的重复性,而在0/1背包问题中,每个物品只能装一次,因此,
* 为了实现0/1背包问题,我们需要知道之前装过什么,之后就不能再装同一个物品了。
* 0/1背包问题的算法设计思路很清晰。我们把过程拆开来看,
* 按照动态规划的子结构要求,在给定背包容量的情况下,
*
* 我们要知道K[x,j]和K[x,j-1]的关系。容易发现如果不考虑第j个物品在K[x,j]的装配方案中的话,有:
* K[x,j] = K[x,j-1];
* 而如果要考虑第j个物品在K[x,j]的装配方案中的话,有:
* K[x,j] = Max{K[x,j],K[x - Wj,j - 1] + Vj},这里类似于完全背包问题,改改参数即可。
*
* 即判断是否装入第j个物品,在之前的哪里装入第j个物品。如此一来,这样一来永远都不会发生重复添置物品的情况了。
*/
private static int[,] K;
private static List<Item>[,] PackSolutions;
public static int SolveZero_OneKnapsack(Item[] items, int capacity)
{
int n = items.Length;
K = new int[capacity + 1, n + 1];
PackSolutions = new List<Item>[capacity + 1, n + 1];
//初始化
for(int i = 0; i < capacity + 1; i++)
{
for(int j = 0;j < n + 1; j++)
{
K[i, j] = 0;
PackSolutions[i, j] = new List<Item>();
}
}
for(int x = 1; x <= capacity; x++)
{
for(int j = 1; j <= n; j++)
{
//还未加入第j个item时,为其Solutions赋初值,因为j不一定会加入,此时是没考虑j在K[x,j]中
K[x, j] = K[x, j - 1];
PackSolutions[x, j - 1].ForEach(item => PackSolutions[x, j].Add(item));
if (items[j - 1].Weight <= x)//items[j-1] 就是第j个物品
{
int q = K[x - items[j - 1].Weight, j - 1] + items[j - 1].Value;
//开始考虑是否加入第j个物品
if (q > K[x, j])
{
K[x, j] = q;
//在K[x - items[j - 1].Weight, j - 1]的基础上添加Item(在哪里添加第j个物品)
PackSolutions[x, j].Clear();
PackSolutions[x - items[j - 1].Weight, j - 1].ForEach(item => PackSolutions[x, j].Add(item));
PackSolutions[x, j].Add(items[j - 1]);
}
}
}
}
return K[capacity, n];
}
public static void PrintPackSolutions(Item[] items, int capacity)
{
for (int i = 1; i <= capacity; i++)
{
Console.WriteLine("---------------------------------------------------");
Console.WriteLine("容纳量x为:" + i);
for (int j = 1; j <= items.Length; j++)
{
Console.WriteLine("选择前" + j + "个元素的方案:");
for (int k = 0; k < PackSolutions[i, j].Count; k++)
{
Console.Write(PackSolutions[i, j][k].Name);
Console.Write(" ");
}
Console.WriteLine(" ");
}
}
}
}
}
using System;
namespace Knapsack
{
class Program
{
static void Main(string[] args)
{
Item[] items = new Item[3];
items[0] = new Item {
Name = "乌龟", Weight = 1, Value = 1 };
items[1] = new Item {
Name = "灯泡", Weight = 2, Value = 4 };
items[2] = new Item {
Name = "西瓜", Weight = 3, Value = 6 };
/*int maxValue = UnboundedKnapsack.SolveUnboundedKnapsack(items, 4);
Console.WriteLine("最大价值:" + maxValue);
UnboundedKnapsack.PrintPackSolutions();*/
int maxValue = Zero_OneKnapsack.SolveZero_OneKnapsack(items, 3);
Console.WriteLine("最大价值:" + maxValue);
Zero_OneKnapsack.PrintPackSolutions(items, 3);
}
}
}