用遞歸求解問題時,反覆的嵌套會浪費內存。並且更重要的一點是,以前計算的結果沒法有效存儲,下一次碰到同一個問題時還須要再計算一次。例如遞歸求解 Fibonacci 數列,假設求第 n 位(從 1 開始)的值,C 代碼以下:web
#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 左右試試,這是消耗的時間就是秒級了。總共的求解步驟以下:svg
若是想把每次求解的結果保存下來,就須要一個長度位 n 的數組,從頭開始把每個位置的值保存下來,這樣求解後面的值的時候就能夠用了。函數
對於遞歸,只要寫好了退出條件,以後不停的調用自身便可,最終到達退出條件時,逐個退出函數。spa
動態規劃則是從頭開始,用循環達到目的。code
動態規劃和遞歸的最大的區別,就是在碰到重疊子問題(Overlap Sub-problem)時,是否只須要計算一次。xml
#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。token
分析:對於任意第 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; }