C#内功修炼(算法)——动态规划(四)

动态规划(Dynamic Programming

动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。

动态规划和分治法相似,都是通过组合子问题的解来求解原问题。分治法将问题划分成互不相交的子问题,递归求解子问题,再将他们的解组合起来,求出原问题的解。与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题。在这种情况下,分治算法会做出许多不必要的工作,它会反复的求解那些公共子问题。而动态规划算法对每个子子问题只求解一次,将结果保存到表格中,从而无需每次求解一个子子问题都要重新计算。


动态规划 钢条切割问题

假定我们知道sering公司出售一段长度为I英寸的钢条的价格为pi(i=1,2,3….)钢条长度为整英寸如图给出价格表的描述(任意长度的钢条价格都有)


先给我们一段长度为n的钢条,问怎么切割,获得的收益最大rn?

考虑n=4的时候

假如一个最优解把n段七个成了k段(1<=k<=n,那么最优切割方案:

        

最大收益:

        

        

第一种求最优解方案:

对于 r n(n>=1),最优切割收益:

        

将切割方案分成下面几种

1,不切割  收益为pn

2,将它切割成两半,切割成两半的情况有,对每种情况求最优解

      (1,n-1) (2,n-2) (3,n-3) (4,n-4) ..... (n-1,1)

      对这两半分别求最优解,最优解的和就是当前情况的最优解

第二种求最优解方案:

我们从钢条的左边切下长度为i的一段,只对右边剩下长度为n-i的一段继续进行切割,对左边的不再切割。这样,不做任何切割的方案就是:当第一段长度为n的时候,收益为pn,剩余长度为0,对应的收益为0。如果第一段长度为i,收益为pi

        

代码实现 自顶向下递归实现

[csharp]  view plain  copy
  1. private void Start()  
  2. {  
  3.     int n = 5;  
  4.     int[] price = { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };  
  5.     UpDown(n, price);  
  6. }  
  7.   
  8. //递归实现  
  9. private int UpDown(int n, int[] price)  
  10. {  
  11.     if (n == 0) return 0;  
  12.     int tempMaxPrice = 0;  
  13.     for (int i = 1; i < n + 1; i++)  
  14.     {  
  15.         int maxPrice = price[i] + UpDown(n - 1, price);  
  16.         if (maxPrice > tempMaxPrice)  
  17.         {  
  18.             tempMaxPrice = maxPrice;  
  19.         }  
  20.     }  
  21.     return tempMaxPrice;  
  22. }  

分析效率,关于上述方法的运行性能时间问题。


动态规划的方法进行求解

上面的方法之所以效率很低,是因为它反复求解相同的子问题。因此,动态规划算法安排求解的顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,不必重新计算。因此动态规划的方法是付出额外的内存空间来节省计算时间。

动态规划有两种等价的实现方法(我们使用上面的钢条切割问题为例,实现这两种方法)

第一种方法是带备忘的自顶向下法

  此方法依然是按照自然的递归形式编写过程,但过程中会保存每个子问题的解(通常保存在一个数组中)。当需要计算一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;如果没有保存过此解,按照正常方式计算这个子问题。我们称这个递归过程是带备忘的。

[csharp]  view plain  copy
  1. //动态规划  
  2. private void Start()  
  3. {  
  4.     int n = 5;  
  5.     int[] result = new int[n + 1];  //备忘录  
  6.     int[] price = { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };  
  7.     UpDown(n, price, result);  
  8. }  
  9.   
  10. //带备忘录  
  11. private int UpDown(int n, int[] price, int[] result)  
  12. {  
  13.     if (n == 0) return 0;  
  14.     if (result[n] != 0)  
  15.     {  
  16.         return result[n];  
  17.     }  
  18.     int tempMaxPrice = 0;  
  19.     for (int i = 1; i < n + 1; i++)  
  20.     {  
  21.         int maxPrice = price[i] + UpDown(n - 1, price, result);  
  22.         if (maxPrice > tempMaxPrice)  
  23.         {  
  24.             tempMaxPrice = maxPrice;  
  25.         }  
  26.     }  
  27.     result[n] = tempMaxPrice;  
  28.     return tempMaxPrice;  
  29. }  

第二种方法是自底向上法

  首先恰当的定义子问题的规模,使得任何问题的求解都只依赖于更小的子问题的解。因而我们将子问题按照规模排序,按从小到大的顺序求解。当求解某个问题的时候,它所依赖的更小的子问题都已经求解完毕,结果已经保存。 

[csharp]  view plain  copy
  1. private int BottonUp(int n, int[] price, int[] result)  
  2. {  
  3.     for (int i = 1; i < n + 1; i++)  
  4.     {  
  5.         int tempMax = -1;  
  6.         for (int j = 1; j <= i; j++)  
  7.         {  
  8.             int maxPrice = price[j] + result[i - j];  
  9.             if (maxPrice > tempMax)  
  10.             {  
  11.                 tempMax = maxPrice;  
  12.             }  
  13.         }  
  14.         result[i] = tempMax;  
  15.     }  
  16.     return result[n];  
  17. }  


动态规划 背包问题

问题描述:

  假设现有容量m kg的背包,另外有i个物品,重量分别为w[1] w[2] ... w[ikg,价值分别为p[1] p[2] ... p[i(元),将哪些物品放入背包可以使得背包的总价值最大?最大价值是多少?

(示例一:m=10  i=3  重量和价值分别为3kg-4 4kg-5 5kg-6  

1,穷举法(把所有情况列出来,比较得到 总价值最大的情况)

[csharp]  view plain  copy
  1. private void Start()  
  2. {  
  3.     int m;  
  4.     int[] w = { 0, 3, 4, 5 };  
  5.     int[] p = { 0, 4, 5, 6 };  
  6. }  
  7.   
  8. private int Exhaustivity(int m, int[] w, int[] p)  
  9. {  
  10.     int i = w.Length - 1;//物品的个数  
  11.   
  12.     int maxPrice = 0;  
  13.     for (int j = 0; j < Mathf.Pow(2, m); j++)  
  14.     {  
  15.         //取得j 上某一个位的二进制值   
  16.         int weightTotal = 0;  
  17.         int priceTotal = 0;  
  18.         for (int number = 1; number <= i; number++)  
  19.         {  
  20.             int result = Get2(j, number);  
  21.             if (result == 1)  
  22.             {  
  23.                 weightTotal += w[number];  
  24.                 priceTotal += p[number];  
  25.             }  
  26.         }  
  27.         if (weightTotal <= m && priceTotal > maxPrice)  
  28.         {  
  29.             maxPrice = priceTotal;  
  30.         }  
  31.     }  
  32.     return maxPrice;  
  33. }  
  34.   
  35. //取得j上第number位上的二进制值,是1还是0  
  36. public static int Get2(int j, int number)  
  37. {  
  38.     int A = j;  
  39.     int B = (int)Mathf.Pow(2, number - 1);  
  40.     int result = A & B;  
  41.     if (result == 0)  
  42.         return 0;  
  43.     return 1;  
  44. }  

  如果容量增大,物品增多,这个方法的运行时间将成指数增长


2,动态规划算法

  我们要求得i个物体放入容量为m(kg)的背包的最大价值(记为c[im])。在选择物品的时候,对于每种物品i只有两种选择,即装入背包或不装入背包。某种物品不能装入多次(可以认为每种物品只有一个),因此该问题被称为0-1背包问题

  对于c[im]有下面几种情况:

  ac[i,0]=c[0,m]=0

  bc[i,m]=c[i-1,m]  w[i]>m(最后一个物品的重量大于容量,直接舍弃不用)

        w[i]<=m的时候有两种情况,一种是放入i,一种是不放入i

              不放入i c[i,m]=c[i-1,m]

              放入i   c[i,m]=c[i-1,m-w[i]]+p[i]

       c[i,m]=max(不放入i,放入i)

[csharp]  view plain  copy
  1. private void Start()  
  2. {  
  3.     int m;  
  4.     int[] w = { 0, 3, 4, 5 };  
  5.     int[] p = { 0, 4, 5, 6 };  
  6.     BottomUp(10, 3, w, p);  
  7. }  
  8.   
  9. private int[,] result = new int[11, 4];  
  10. private int BottomUp(int m, int i, int[] w, int[] p)  
  11. {  
  12.     if (result[m, i] != 0) return result[m, i];  
  13.     for (int tempM = 1; tempM < m + 1; tempM++)  
  14.     {  
  15.         for (int tempI = 1; tempI < i + 1; tempI++)  
  16.         {  
  17.             if (result[tempM, tempI] != 0) continue;  
  18.             if (w[tempI] > tempM)  
  19.             {  
  20.                 result[tempM, tempI] = result[tempM, tempI - 1];  
  21.             }  
  22.             else  
  23.             {  
  24.                 int maxValue1 = result[tempM - w[tempI], tempI - 1] + p[tempI];  
  25.                 int maxValue2 = result[tempM, tempI - 1];  
  26.                 if (maxValue1 > maxValue2)  
  27.                 {  
  28.                     result[tempM, tempI] = maxValue1;  
  29.                 }  
  30.                 else  
  31.                 {  
  32.                     result[tempM, tempI] = maxValue2;  
  33.                 }  
  34.             }  
  35.         }  
  36.     }  
  37.     return result[m, i];  
  38. }  
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值