赞
踩
用递归求解问题时,反复的嵌套会浪费内存。而且更重要的一点是,之前计算的结果无法有效存储,下一次碰到同一个问题时还需要再计算一次。例如递归求解 Fibonacci 数列,假设求第 n 位(从 1 开始)的值,C 代码如下:
#include <stdio.h>
int fib(int n) {
if (n < 3) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
int main(void) {
int ret = fib(5);
printf("fib ret is: %d\n", ret);
return 0;
}
上面的代码,每个运算节点都需要拆成两步运算,时间复杂度位 O(2^n)。
你可以把 n 改为 40 左右试试,这是消耗的时间就是秒级了。总共的求解步骤如下:
如果想把每次求解的结果保存下来,就需要一个长度位 n 的数组,从头开始把每一个位置的值保存下来,这样求解后面的值的时候就可以用了。
对于递归,只要写好了退出条件,之后不停的调用自身即可,最终到达退出条件时,逐个退出函数。
动态规划则是从头开始,用循环达到目的。
动态规划和递归的最大的区别,就是在碰到重叠子问题(Overlap Sub-problem)时,是否只需要计算一次。
#include <stdio.h> int fib(int n) { int i; int dp_opt[n]; dp_opt[0] = 1; dp_opt[1] = 1; for (i = 2; i < n; i++) { dp_opt[i] = dp_opt[i - 1] + dp_opt[i - 2]; } return dp_opt[n - 1]; } int main(void) { int ret = fib(5); printf("fib ret is: %d\n", ret); return 0; }
上面代码的时间复杂的是 O(n)。
题目:从集合中,任取任意多个非相邻的数字并求和,找出最大的和。例如,对于 {1, 9, 2, 5, 4},最大的和是 14。
分析:对于任意第 n 位数字,都有两种情况,只需要取值最大的那种即可:
递归退出条件:
递归循环:
int recursive(int arr[], int n, int i) {
if (i == 0)
return arr[0];
if (i == 1)
return arr[0] > arr[1] ? arr[0] : arr[1];
int before = recursive(arr, n, i - 2) + arr[i];
int cur = recursive(arr, n, i - 1);
return cur > before ? cur : before;
}
为了确保每个最小子问题都只计算一次,就必须把计算的结果保存起来。另外,跟递归的逆序求解方向相反,动态规划从第一个元素开始,依次计算每个元素的最大和:
int dp_opt(int arr[], int n, int x) { int i; int before, cur; int opt[n]; for (i = 0; i < n; i++) { opt[i] = 0; } opt[0] = arr[0]; opt[1] = arr[0] > arr[1] ? arr[0] : arr[1]; for (i = 2; i < n; i++) { before = opt[i - 2] + arr[i]; cur = opt[i - 1]; opt[i] = cur > before ? cur : before; } return opt[x]; }
#include <stdio.h> // 递归解法 int recursive(int arr[], int n, int i) { if (i == 0) return arr[0]; if (i == 1) return arr[0] > arr[1] ? arr[0] : arr[1]; int before = recursive(arr, n, i - 2) + arr[i]; int cur = recursive(arr, n, i - 1); return cur > before ? cur : before; } // 动态规划 int dp_opt(int arr[], int n, int x) { int i; int before, cur; int opt[n]; for (i = 0; i < n; i++) { opt[i] = 0; } opt[0] = arr[0]; opt[1] = arr[0] > arr[1] ? arr[0] : arr[1]; for (i = 2; i < n; i++) { before = opt[i - 2] + arr[i]; cur = opt[i - 1]; opt[i] = cur > before ? cur : before; } return opt[x]; } int main() { int i; int n = 7; int arr[] = {1, 2, 4, 1, 7, 8, 3}; for (i = 0; i < 7; i++) { printf("recursive ret is: %d, dp_opt ret is: %d\n", recursive(arr, n, i), dp_opt(arr, n, i)); } return 0; }
例如,对于 {2, 5, 8, 22, 9},给定值位 15,则可以找到组合 {2, 5, 8} 满足条件。
要判断多个元素之和是否等于某个值 sum,则对于任意的元素 n,情况如下:
递归退出条件:
递归循环:
int recursive(int arr[], int n, int sum) {
if (n == 0)
return arr[0] == sum;
if (sum == 0)
return 1;
if (sum < arr[n])
return recursive(arr, n - 1, sum);
return recursive(arr, n - 1, sum) || recursive(arr, n - 1, sum - arr[n]);
}
有了上面的递归的思路后,再把递归转为动态规划。
上一个例子中,求非相邻元素最大和时,每个元素的位置上只需要保存当前元素的最大值,所以创建一个一维数组即可。而现在已知元素之和,
例如,对于集合 {3, 5, 9, 1, 2},如果 sum = 6,则需要创建 dp_subset[5][7] 数组:
if (i == 0) return arr[0] == sum;
)if (sum == 0) return true;
)arr[i] | i \ sum | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|---|
3 | 0 | F | F | F | T | F | F | F |
5 | 0 | T | ||||||
9 | 0 | T | ||||||
1 | 0 | T | ||||||
2 | 0 | T |
在已经初始化的二维数组基础上,参考递归体就可以完成迭代的代码。另外,还有一个递归结束条件也放在迭代里面。
if (arr[i] > sum) recursive(arr, n - 1, sum);
int dp_subset(int arr[], int n,
#include <stdio.h> // 递归解法 int recursive(int arr[], int n, int sum) { if (n == 0) return arr[0] == sum; if (sum == 0) return 1; if (sum < arr[n]) return recursive(arr, n - 1, sum); return recursive(arr, n - 1, sum) || recursive(arr, n - 1, sum - arr[n]); } // 动态规划 int dp_subset(int arr[], int n, int sum) { int subset[n][sum + 1]; int i, s; for (i = 0; i < n; i++) { for (s = 0; s <= sum; s++) subset[i][s] = 0; } for (i = 0; i < n; i++) { subset[i][0] = 1; } subset[0][0] = 0; subset[0][arr[0]] = 1; for (i = 1; i < n; i++) { for (s = 1; s <= sum; s++) { if (arr[i] > sum) { subset[i][s] = subset[i - 1][s]; } else { subset[i][s] = subset[i - 1][s] || subset[i - 1][s - arr[i]]; } } } return subset[n - 1][sum]; } int main() { int n = 7; int arr[] = {1, 2, 4, 7, 8, 3, 32}; int sum = 3; printf("recursive ret is: %d, dp_opt ret is: %d\n", recursive(arr, n, sum), dp_subset(arr, n, sum)); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。