当前位置:   article > 正文

每天一种算法分析-动态规划_算法分析动态规划

算法分析动态规划

动态规划常常用在一些贪心算法无法得到正确答案的题目上,因为动态算法对思维方面有一定要求而且代码量比较小,在笔试题中特别常见。

算法思想

动态规划算法的问题有下面4个特点:

  1. 算法目的是求一个最优解(最大值或者最小值)
  2. 该问题可以被分解为若干个小问题,这若干个小问题之间包含很多共同的更小的问题
  3. 大问题的最优解依赖与小问题的最优解
  4. 由于小问题在很多个大问题中重复出现,所以我们可以把小问题的解储存下来,从大往小分析问题,从小往大求解问题(比如斐波那契数列的求解)

比如我们在解决一个剪绳子的问题时(题目来自剑指offer):给一个int n长的绳子分为int m段,求剪开的每段绳子的乘积最大是多少。比如长3的绳子分为2段,可以剪为长1,长2的两段,最大乘积就是1 × 2 = 2 。在用动态规划方法思考这个问题的时候,我们每一次剪开绳子面临若干个选择,比如对于n长的绳子我们在剪第一刀的时候有n - 1个选择:剪在位置1,2,3...n-1。由于我们不知道那个位置是最优解,所以我们只能都尝试一遍,然后寻找最大值。而且我们发现,剪绳子这个问题可以分解为若干个小问题,这些小问题之间有相互重叠的更小的问题:如果绳子总长为8 也就是求解问题f(8),我们可以把他剪成3和5两段。然后分别求解这两个问题:f(3) f(5),在求解 f(5) 的时候我们又能分为长为2和长为3的两段,也就是问题 f(2) f(3),问题f(3) 可能被分解为问题 f(2) f(1)。也就是说问题 f(5) 包含问题 f(3)而且还包含问题f(3) 的子问题。这就是上文中提到的动态规划算法的前3个特点。

对于特点4我们看下面的例子:

斐波那契数列 - 理解从小往大求解

这是一个很典型的例子,我们写斐波那契数列时最直观的算法就是利用递归:

  1. long long Fibonacci(unsigned int n) {
  2. if (n <= 0) return 0;
  3. if (n == 1) return 1;
  4. return Fibonacci(n - 1) + Fibonacci(n - 2);
  5. }

但是很明显递归不是一个很有效的算法,求解 f(3) 会用到 f(1) 和 f(2),求解 f(4) 也会用到 f(1) 和 f(2),但是在递归的方法中我们在求解 f(5) 时多次重复计算 f(1) 和 f(2) 这两个值,也就是说在求解一个大问题 f(5) 时,这个大问题可以被分解为两个小问题求解 f(3) f(4),这两个小问题又包含很多跟小的问题 f(1) 和 f(2) ,所以我们通过把小问题的解储存下来,从小往大求解:

  1. long long Fibonacci2(unsigned int n) {
  2. if (n <= 0) return 0;
  3. if (n == 1) return 1;
  4. if (n == 2) return 1;
  5. int tmp1 = 1;
  6. int tmp2 = 1;
  7. int tmp3 = 0;
  8. for (int i = 2; i < n; i++) {
  9. tmp3 = tmp1 + tmp2;
  10. tmp1 = tmp2;
  11. tmp2 = tmp3;
  12. }
  13. return tmp3;
  14. }

动态规划算法常见问题

数塔问题

给定如图(图片来自TheOneGIS的博客https://blog.csdn.net/theonegis/article/details/45801201)一个数塔,从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大。

用data数组保存输入,直接修改data数组保存从每个节点出发一直到底层的最大路径。最低一层的最大路径就是原值所以直接跳过。

对第 i 层( i from layer - 2 to 0 ) 的第 j 项( j from 0 to i + 1 ) (比如对第4层的第 0, 1, 2, 3 项)比较其左右两个子节点的值,然后把最小值加上改节点的路径长度保存在该节点中:

data[i][j] += max(data[i + 1][j], data[i + 1][j + 1]);
  1. #include <iostream>
  2. using namespace std;
  3. int solution(int** data, int layer) {
  4. int temp_max = 0;
  5. for (int i = layer - 2; i >= 0; --i) {
  6. for (int j = 0; j< i + 1; ++j) {
  7. temp_max = max(data[i + 1][j], data[i + 1][j + 1]);
  8. data[i][j] += temp_max;
  9. }
  10. }
  11. return data[0][0];
  12. }
  13. int main() {
  14. cout << "几层?" << endl;
  15. int layer = 5;
  16. cin >> layer;
  17. cout << "输入塔的值" << endl;
  18. int** data = new int*[layer];
  19. for (int i = 0; i < layer; ++i) {
  20. data[i] = new int[layer];
  21. }
  22. for (int i = 0; i < layer; ++i) {
  23. for (int j = 0; j < layer; ++j) {
  24. data[i][j] = 0;
  25. }
  26. }
  27. for (int i = 0; i < layer; ++i) {
  28. for (int j = 0; j < i + 1; ++j) {
  29. int tmp;
  30. cin >> tmp;
  31. data[i][j] = tmp;
  32. }
  33. }
  34. solution(data, layer);
  35. for (int i = 0; i < layer; ++i) {
  36. for (int j = 0; j < layer; ++j) {
  37. cout << data[i][j] << " ";
  38. }
  39. cout << endl;
  40. }
  41. for (int i = 0; i < layer; ++i) {
  42. delete[] data[i];
  43. }
  44. delete[] data;
  45. return 0;
  46. }

硬币问题

给予无限多不同面值的硬币若干种,如何用若干种硬币组合为某种面额的钱,使硬币的的个数最少(如果无法组合就输出999999)

题目中加入我们要组合为20元,可能会分为先组合1元再组合19元、先组合2元再组合18元......很多种情况,根据从小往大解决问题的思路,我们先解决组合为1元,组合为2元的问题,然后逐级向上求解

在下面代码的第一个 for 循环是将result数组中可以有对应面值的组合的最少硬币个数置1(比如我们提供了面值为10的货币,result[10] = 1,因为支付10元时直接付一张10块钱的RMB就可以啦)

第二个 for 循环用来更新需要支付某一值的钱数时需要的最少硬币,无解就保持数组默认值MAX不变,在扫描时假如当前求解需要支付8元的情况就遍历求解 f(1) + f(7), f(2) + f(6), f(3) + f(5), f(4) + f(4) 然后选择其中的最小值更新 f(8) 的值

  1. #include <iostream>
  2. #define MAX 999999
  3. using namespace std;
  4. void solution(int kind, const int* value, int cost, int* result) {
  5. for (int i = 0; i < kind; ++i) {
  6. result[value[i]] = 1;
  7. }
  8. int tmp;
  9. for (int i = 1; i <= cost; ++i) {
  10. for (int j = 1; j <= int(i / 2); ++j) {
  11. tmp = result[j] + result[i - j];
  12. if (tmp < result[i]) result[i] = tmp;
  13. }
  14. }
  15. }
  16. int main() {
  17. cout << "几种?" << endl;
  18. int kind = 0;
  19. cin >> kind;
  20. cout << "输入面值" << endl;
  21. int* value = new int[kind];
  22. for (int i = 0; i < kind; ++i) {
  23. cin >> value[i];
  24. }
  25. cout << "输入饮料价值" << endl;
  26. int cost;
  27. cin >> cost;
  28. int* result = new int[cost + 1];
  29. for (int i = 0; i <= cost; ++i) {
  30. result[i] = MAX;
  31. }
  32. solution(kind, value, cost, result);
  33. for (int i = 0; i <= cost; ++i) {
  34. cout << result[i] << " ";
  35. }
  36. delete[] value;
  37. delete[] result;
  38. return 0;
  39. }

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/567557
推荐阅读
相关标签
  

闽ICP备14008679号