C#笔记:动态规划算法
- 2019 年 11 月 22 日
- 筆記
一个复杂的问题,通常逐级会分解成一系列小问题。而且很多情况下,某些小问题是相似的。但是使用传统的递归,会将相同相似的小问题进行重复的计算。这样浪费了算力。 所谓动态规划算法,实质是使用一个表,记录下所有小问题的值。如果在计算中再次遇到相同问题,则直接取值。不做计算。显然,如果每个小问题都是不同的,那么此算法没有优势。 比如斐波那契数列,就是一个简单的例子。 定义:
Fab(n)= Fab(n-1)+Fab(n-2) Fab(1)=Fab(2)=1;
实现1:
static int GetFab(int n) { if (n == 1) return 1; if (n == 2) return 1; return GetFab(n - 1) + GetFab(n - 2); }
假如我们求Fab(5) 。那我们需要求Fab(4) +Fab(3)。 Fab(4)=Fab(3)+Fab(2)…..显然。 Fab(3)被计算机不加区别的计算了两次。而且随着数字的增大,计算量是指数增长的。 如果我们使用一个数组,记录下Fab的值。当Fab(n)!=null 时。直接读取。那么,我们就能把时间复杂度控制在 n 以内。 实现2:
static int[] fab = new int[6]; static int GetFabDynamic(int n) { if (n == 1) return fab[1] = 1; if (n == 2) return fab[2] = 1; if (fab[n] != 0)//如果存在,就直接返回。 { return fab[n]; } else //如果不存在,就进入递归,并且记录下求得的值。 { return fab[n] = GetFabDynamic(n - 1) + GetFabDynamic(n - 2); } }
这就是,动态规划算法的 备忘录模式。只需要把原来的递归稍微修改就行了。 下面是0-1背包问题的解法。可以对比一下。(一个限重w的背包,有许多件物品。sizes[n]保存他们的重量。values[n]保存它们的价值。求不超重的情况下背包能装的最大价值)
static int[] size = new int[] { 3, 4, 7, 8, 9 };// 5件物品每件大小分别为3, 4, 7, 8, 9 且是不可分割的 0-1 背包问题 static int[] values = new int[] { 4, 5, 10, 11, 13 };//// 5件物品每件的价值分别为4, 5, 10, 11, 13 static int capacity = 16; static int[,] dp = new int[6, capacity + 1]; static int knapsack(int n, int w) { if (w < 0) return -10000; if (n == 0) return 0; if (dp[n, w] != 0) { return dp[n, w]; } else { return dp[n, w] = Math.Max(knapsack(n - 1, w), knapsack(n - 1, w - size[n - 1]) + values[n - 1]); /* * knapsack(n,w) 指在前N件物品在W剩余容量下的最大价值。 * 这个公式的意思是,对于某一件物品, * 1、如果选择装进去,那么,当前价值=前面的n-1件物品在空位w - size(n)下的最大价值(因为空位需要留出,空位也隐含了价值)。 * 再加上这件物品的价值。等于 knapsack(n - 1, w - size[n - 1]) + values[n - 1] * 2、 如果我们选择不装进去,那么,在n-1物品的情况下空位仍然在,当前价值 = 前面n-1件物品在空位w下的最大价值。 * 等于knapsack(n - 1, w) * 注意:随着演算,某一情况下的价值不会一成不变。 * 此时我们做出决策:到底是在装入时的价值大,还是不装入时的价值大呢?我们选择上面两种情况中值较大的。并记录在案。 * 最后dp[N,M]就是我们要求的值。 */ } }