问题描述:
假设现有容量m kg的背包,另外有n个物品,重量分别为w[1] w[2] ... w[i] (kg),价值分别为p[1] p[2] ... p[i] (元),将哪些物品放入背包可以使得背包的总价值最大?最大价值是多少?
(示例一:m=10 n=3 重量和价值分别为 3kg-4元 4kg-5元 5kg-6元 )
1,穷举法(把所有情况列出来,比较得到 总价值最大的情况)
2、动态规划法。
穷举法 分析:
总物品个数为 n, 每个物品有两种可能性(拿,不拿),所以总的组合有 种;遍历这
可能性就可以找出最大价值。
怎么遍历?
不拿我们用 0 表示,不拿 用 1 表示,这时候我们可以想到用二进制来表示
(示例一:m=10 n=3 重量和价值分别为 3kg-4元 4kg-5元 5kg-6元 )
1 1 1 表示三个物品都拿
0 0 0表示三个物品都不拿
0 0 1 只拿第一个
依此类推出各种可能性
怎么知道第 i 个位置上是 0 还是 1 ?
这时候就要使用 & 运算符,
等于 1 二进制为 001 1 0 1 & 0 0 1 = 0 0 1 > 0 说明拿了第一个物品
等于 2 二进制为 010 1 0 1 & 0 1 0 = 0 0 0 = 0 说明第二个没有拿
代码实现
class Program
{
static void Main(string[] args)
{
int[] w = { 0, 3, 4, 5 };
int[] p = { 0, 4, 5, 6 };
Console.WriteLine(Exhaustion(10, w, p));
Console.WriteLine(Exhaustion(3, w, p));
Console.WriteLine(Exhaustion(4, w, p));
Console.WriteLine(Exhaustion(7, w, p));
Console.ReadKey();
}
// 穷举法
private static int Exhaustion(int m, int[] w, int[] p)
{
int len = w.Length;
int maxPrice = 0;
// 总共有 Math.Pow(2, len) 中可能性
for (int i = 0; i < Math.Pow(2, len); i++)
{
int tmpPrice = 0;
int tmpW = 0;
// 对第 i 种可能性的 获取二进制每个位置的值 ,如果是 1 就是选中 是 0 就是不选中
for (int j = 0; j < len; j++)
{
// 获取二进制 i 上面某一位的二进制值
if (GetBinary(i, j) == 1)
{
tmpW += w[j];
tmpPrice += p[j];
}
}
if (tmpW <= m && tmpPrice > maxPrice)
{
maxPrice = tmpPrice;
}
}
return maxPrice;
}
// 获取 n 值的二进制 上的第 x 位的值
private static int GetBinary(int n,int x)
{
int a = n;
int b = (int)Math.Pow(2,x);
int result = a & b;
if (result == 0)
return 0;
return 1;
}
}
该方法的缺点就是 ,总个数每多一个遍历次数就是成指数增长的,性能很差。
方法二 动态规划算法
对动态规划不熟悉的可以看下这篇 动态规划 钢条切割问题
思路:
假设我们有 n 个物品放入容量为 m 的背包中,那么最大价值记为 c[n,m]。
每种物品的选择有两种(拿,不拿)。
对与 c[n,m] 有
1、c[n,0] = c[0,m] = 0 背包的容纳为 0 ,或者 拿 0 个物品 得到的价值都为0
2、w[n] > m 当第n个的重量大于总量m时 c[n,m] = c[n-1,m] ,因为 最后一个(第n个)放不进去要被舍弃掉,最大价值责在 c[n - 1,m]中取得。
若 w[n] <= m 时:
a、放入 c[n,m] = p[n] + c[ n-1 , m - w[n] ]
b、不放入 c[n,m] = c[n-1,m]
自顶向下法代码实现:
class Program
{
static void Main(string[] args)
{
int[] w = { 0, 3, 4, 5 };
int[] p = { 0, 4, 5, 6 };
Console.WriteLine(UpDown(3, 10, w, p));
Console.WriteLine(UpDown(3, 3, w, p));
Console.WriteLine(UpDown(3, 4, w, p));
Console.WriteLine(UpDown(3, 7, w, p));
Console.ReadKey();
}
// 自顶向下 动态规划
private static int UpDown(int n,int m,int[] w,int[] p)
{
// c[n,0] = c[0,m] = 0 背包的容纳为 0 ,或者 拿 0 个物品 得到的价值都为0
if (0 == n || 0 == m) return 0;
int maxPrice = 0;
if (w[n] > m)
{
// 当第n个的重量大于总量m时 c[n,m] = c[n-1,m] ,因为 最后一个(第n个)放不进去要被舍弃掉,最大价值责在 c[n - 1,m]中取得。
return UpDown(n - 1, m, w, p);
}
else // 当第n个的重量小于于总量m时
{
// 不放入价格 c[n,m] = c[n-1,m]
int tmpPrice0 = UpDown(n - 1, m, w, p);
// 放入价格 c[n,m] = p[n] + c[ n-1 , m - w[n] ]
int tmpPrice1 = p[n] + UpDown(n - 1, m - w[n], w, p);
// 谁大选谁
maxPrice = tmpPrice0 > tmpPrice1 ? tmpPrice0 : tmpPrice1;
}
return maxPrice;
}
}
接下来我们进行优化 优化思路可以参考 动态规划 钢条切割问题 我就直接上代码了
class Program
{
static void Main(string[] args)
{
int[] w = { 0, 3, 4, 5 };
int[] p = { 0, 4, 5, 6 };
int m = 10; // 背包的最大容量
int n = w.Length; // 物品总个数
int[,] optimumSolution = new int[m + 1 , n];
Console.WriteLine(UpDown(3, 10, w, p, optimumSolution));
Console.WriteLine(UpDown(3, 3, w, p, optimumSolution));
Console.WriteLine(UpDown(3, 4, w, p, optimumSolution));
Console.WriteLine(UpDown(3, 7, w, p, optimumSolution));
Console.ReadKey();
}
// 自顶向下 动态规划
private static int UpDown(int n, int m, int[] w, int[] p, int[,] optimumSolution)
{
// c[n,0] = c[0,m] = 0 背包的容纳为 0 ,或者 拿 0 个物品 得到的价值都为0
if (0 == n || 0 == m) return 0;
if (optimumSolution[m,n] != 0)
{
return optimumSolution[m,n];
}
int maxPrice = 0;
if (w[n] > m)
{
// 当第n个的重量大于总量m时 c[n,m] = c[n-1,m] ,因为 最后一个(第n个)放不进去要被舍弃掉,最大价值在 c[n - 1,m]中取得。
if (optimumSolution[m,n - 1] == 0)
optimumSolution[m,n] = UpDown(n - 1, m, w, p, optimumSolution);
return optimumSolution[m, n];
}
else // 当第n个的重量小于于总量m时
{
// 不放入价格 c[n,m] = c[n-1,m]
int tmpPrice0 = 0;
// 放入价格 c[n,m] = p[n] + c[ n-1 , m - w[n] ]
int tmpPrice1 = 0;
if (optimumSolution[m, n - 1] == 0)
optimumSolution[m, n] = UpDown(n - 1, m, w, p, optimumSolution);
if (optimumSolution[m - w[n], n - 1] == 0)
optimumSolution[m - w[n], n - 1] = UpDown(n - 1, m - w[n], w, p, optimumSolution);
tmpPrice0 = optimumSolution[m,n - 1];
tmpPrice1 = p[n] + optimumSolution[m - w[n],n - 1];
// 谁大选谁
maxPrice = tmpPrice0 > tmpPrice1 ? tmpPrice0 : tmpPrice1;
}
return maxPrice;
}
}
自底向上法代码实现:
class Program
{
static void Main(string[] args)
{
int[] w = { 0, 3, 4, 5 };
int[] p = { 0, 4, 5, 6 };
int m = 10; // 背包的最大容量
int n = w.Length; // 物品总个数
int[,] optimumSolution = new int[m + 1 , n];
Console.WriteLine(ButtomUp(10, w, p, optimumSolution));
Console.WriteLine(ButtomUp(3, w, p, optimumSolution));
Console.WriteLine(ButtomUp(4, w, p, optimumSolution));
Console.WriteLine(ButtomUp(7, w, p, optimumSolution));
Console.ReadKey();
}
private static int ButtomUp(int m, int[] w, int[] p, int[,] optimumSolution)
{
if (0 == m) return 0;
for (int i = 1; i <= m; i++)
{
int tmpM = i;
for (int j = 1; j < w.Length; j++)
{
if (w[j] > tmpM)
{
optimumSolution[tmpM, j] = optimumSolution[tmpM , j - 1];
}
else
{
// 不放入
int tmpPrice0 = optimumSolution[tmpM, j - 1];
// 放入
int tmpPrice1 = p[j] + optimumSolution[tmpM - w[j], j - 1];
if (tmpPrice0 > tmpPrice1)
{
optimumSolution[tmpM, j] = tmpPrice0;
}
else
{
optimumSolution[tmpM, j] = tmpPrice1;
}
}
}
}
return optimumSolution[m, w.Length - 1];
}
}