在
bilibili
看到一個博主講的比較好的動態規劃內容,自己對應整理了一下學習筆記,以便鞏固學習
題目一:Fibonacci
代碼簡單實現
/**
* Creation : 2018.11.18 17:03
* Last Reversion : 2018.11.18 17:08
* Author : Lingyong Smile {[email protected]}
* File Type : cpp
* -----------------------------------------------------------------
* Fibonacci
* Everyone knows the Fibonacci sequence. Now you need to enter an integer n.
* Please output the nth item of the Fibonacci sequence (starting at 0 and 0th item is 0).
* 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
* -----------------------------------------------------------------
* Crop right @ 2018 Lingyong Smile {[email protected]}
*/
#include <iostream>
using namespace std;
/**
* 遞歸方式
*/
int FibonacciRecursive(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
return FibonacciRecursive(n - 2) + FibonacciRecursive(n - 1);
}
/**
* 非遞歸:動態規劃
*/
int Fibonacci(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int a = 0;
int b = 1;
int c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
int main() {
int n;
cout << "Please input a int number:";
while (cin >> n) {
// Test Fibonacci function
cout << "Fibonacci(" << n << ") is : " << Fibonacci(n) << endl;
// Test Fibonacci Recursive function
cout << "FibonacciRecursive(" << n << ") is : " << FibonacciRecursive(n) << endl;
cout << "Please input a int number:";
}
// system("pause");
return 0;
}
題目二:最多能賺幾塊錢?
橫軸代表時間,灰色的長方條代表從某一時刻開始的任務,長方條上的數字代表完成這個任務能夠賺幾塊錢,比如最上面的第1個任務,表示從1點開始,4點結束,完成這個任務可以獲得5元錢。其中有一個限制,開始了某一任務後,只有做完這個任務才能做下一個,比如開始做了任務7,則不能做任務4、5、6、8,因爲有時間衝突。現在有一個員工,如何才能賺最多的錢,最多能賺幾塊錢?
從上往下分析,得出遞推公式(選不選)
其中 表示,當我要考慮前 個任務時,它的最優解是什麼,即最多能賺幾塊錢?
例如:當考慮前8個任務時 ,對於任務8有兩種狀態,選和不選。
- 選第8個任務,首先可以賺4塊錢,再加上 ,即加上前面5個任務的最優解,最多能賺幾塊錢。(因爲選了任務8則和任務6和7有時間衝突)
- 不選第8個任務,則就是前面7個任務的最優解
然後選這兩個狀態結果最大的,作爲前8個任務的最優解 ,即得到如下遞歸公式:
整理一下遞推公式:
其中 表示第 個任務的獎勵工資, 表示如果要做第 個任務,則它前面頂多還能做前幾個。例如: 表示如果要做第8個任務,則前面頂多還能做前5個,即加上考慮前5個任務的最優解 。
首先計算 數組,比如 時, 表示如果做任務1,則前面能做的爲0個,以此類推。我們最終要算的是前面8個任務的最優解,即 ,根據遞歸式可以得到如上狀結構,可以發現:(1)首先這是一個找最優解問題。(2)大問題的最優解依賴於子問題的最優解。(3)大問題分解成子問題,子問題間有重複子問題。 這樣的問題就可以使用動態規劃思想來做。從上往下分析問題,從下往上求解問題。從 開始分析計算並保存對應結果。
分析:如果不選任務2,則是 ;如果選任務2,則是 。最後選這兩個狀態的最大值作爲前個任務2的最優解,所以 。表示如果只有前面兩個任務的時候,最多能掙5塊錢。
分析:如果不選任務4,則是 ;如果選任務4,則是 。最後選這兩個狀態的最大值作爲前4個任務的最優解,所以 。表示如果只有前面4個任務的時候,最多能掙9塊錢。
後面以此類推,直到分析完前8個任務。
代碼簡單實現
/**
* Creation : 2019.04.05 14:50
* Last Reversion : 2019.03.05 15:22
* Author : Lingyong Smile {[email protected]}
* File Type : cpp
* -----------------------------------------------------------------
* 最多能賺幾塊錢(動態規劃) 結合筆記題目查看
* -----------------------------------------------------------------
* Crop right @ 2019 Lingyong Smile {[email protected]}
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
int v[9] = {0, 5, 1, 8, 4, 6, 3, 2, 4}; // 注意這裏前面多補了一個0,是爲了方便和筆記對應
int prev[9] = {0, 0, 0, 0, 1, 0, 2, 3, 5}; // 這裏的prev數組是自己預先算好了的
/**
* 遞歸形式
*/
int OPTRecursive(int n) {
if (n == 0)
return 0;
if (n == 1)
return 5;
return max(OPTRecursive(n - 1) , v[n] + OPTRecursive(prev[n]));
}
/**
* 非遞歸形式:(動態規劃)
*/
int OPT(int n) {
if (n == 0)
return 0;
if (n == 1)
return 5;
int *OPT = (int*)malloc(sizeof(int) * (n + 1)); // 這裏多開闢了一個int空間大小,是爲了和公式下標對應
OPT[0] = 0;
OPT[1] = 5;
for (int i = 2; i <= n; i++) {
OPT[i] = max(OPT[i-1], v[i] + OPT[prev[i]]);
}
int res = OPT[n];
free(OPT);
return res;
}
int main() {
int n;
printf("Please input a int number (1~8):\n");
while (~scanf("%d", &n) && n >= 1 && n <= 8) {
printf("%d\n", OPT(n));
}
return 0;
}
題目三:最大和
在如下一堆數字中選出一些數字,如何讓數字之和最大?
限定條件:選出的數字不能是相鄰的。
從上往下分析,得出遞歸公式(選不選)
:表示到下標爲6 這個位置的最佳方案(最優解)是什麼?
從上往下開始分析:對於 我們有兩種狀態,選3,和不選3。即有如上遞歸式。
- 選 ,此時的最佳方案是 。這裏不能選 ,因爲 與 是相鄰的。
- 不選 ,此時最佳方案是
然後選這兩個狀態結果最大的,作爲到下標爲6這個位置的最優解
進而,我們可以得到這樣的遞歸方程:
分析遞歸出口
代碼簡單實現
/**
* Creation : 2019.04.06 11:10
* Last Reversion : 2019.04.06 11:23
* Author : Lingyong Smile {[email protected]}
* File Type : cpp
* -----------------------------------------------------------------
* 題目描述:
* 在如下一堆數字中選出一些數字,如何讓數字之和最大?
* 限定條件:選出的數字不能是相鄰的。
* arr[7] = {1, 2, 4, 1, 7, 8, 3}
* -----------------------------------------------------------------
* Crop right @ 2019 Lingyong Smile {[email protected]}
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
int arr[7] = {1, 2, 4, 1, 7, 8, 3};
/**
* 遞歸形式,不好存在很多重疊子問題
*/
int OPTRec(int n) {
if (n == 0)
return arr[0];
else if (n == 1)
return max(arr[0], arr[1]);
else
return max(OPTRec(n - 2) + arr[n], OPTRec(n - 1));
}
/**
* 非遞歸形式:動態規劃
*/
int OPT(int n) {
if (n == 0)
return arr[0];
else if (n == 1)
return max(arr[0], arr[1]);
int *OPT = (int*)malloc(sizeof(int) * (n + 1)); // 注意這裏開闢的數組大小,n爲下標,從0開始
OPT[0] = arr[0];
OPT[1] = max(arr[0], arr[1]);
for (int i = 2; i <= n; i++) {
OPT[i] = max(OPT[i-2] + arr[i], OPT[i-1]);
}
int res = OPT[n];
free(OPT);
return res;
}
int main() {
int n;
printf("Please input a int number (0~7):\n");
while (~scanf("%d", &n) && n >= 0 && n <= 7) {
printf("%d\n", OPT(n));
}
return 0;
}
題目四:是否存在和爲指定值
在如下一堆數字中選出一些數字求和,使其等於數字 。如果存在這樣的方案則返回 ,否則返回 。
從上往下分析,得出遞歸公式(選不選)
分析:我們定義 表示子集, 表示對前面5個數字取數求和爲9,應該怎麼分配。從上往下分析,當 時,我們有兩種狀態:
- 選 ,此時方案爲 ,選了 之後,只需要前面4個數字取數之和爲7就可以。
- 不選 ,此時方案爲 ,前面4個數字取數之和爲9。
最後只要這兩種方案有一個爲 ,則滿足要求,所以中間用
分析遞歸出口
- 時, 比如 ,表示,2後面的數字已經存方案可以使得取數求和等於 ,即這個時候已滿足要求,返回
- 時,即表示處理到第一個數字,只有當 纔會返回 ,否則返回 。比如,當我們給的數組只有一個元素是,只有當 時才爲
- 時,這時候我們肯定不能選 ,即此時返回
整理一下遞歸公式如下:
動態規劃解法
即非遞歸方式,我們需要藉助二維數組,保存我們的中間過程(中間的這些重疊子問題)。如下設計我們的二維數組:
其中,最左邊列是 數組,右邊是設計一個二維數組,行座標表示下標 ,縱座標表示 ,比如圖中的綠色方框表示 此時,由於 ,所以只能不選 ,即得到當前方案 。明白含義之後,我們來定義我們的出口條件:
- 當 時,只有 時返回 ,其餘都爲 。即我們寫好了 這一行的出口條件
- 當 時,說明已經找到了取數方案,直接返回 。即我們寫好了 這一列的出口條件。
然後按照從左往右的順序,把每一行填好,最後返回最後一個結果即可。
對於 我們規定爲
代碼簡單實現
/**
* Creation : 2019.04.06 11:30
* Last Reversion : 2019.04.06 15:39:
* Author : Lingyong Smile {[email protected]}
* File Type : cpp
* -----------------------------------------------------------------
* 題目描述:
* 在如下一堆數字中選出一些數字求和,能夠等於數字n。如果存在這樣的方案則
* 返回true,否則返回false。
* int arr[] = {3, 34, 4, 12, 5, 2};
* 測試用例:
輸入:
9
10
11
12
13
輸出:
1
1
1
1
0
* -----------------------------------------------------------------
* Crop right @ 2019 Lingyong Smile {[email protected]}
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N_SIZE 100
#define M_SIZE 100
int arr[] = {3, 34, 4, 12, 5, 2};
int subset[N_SIZE][M_SIZE];
/**
* 遞歸形式
*/
int RecSubset(int arr[], int i, int S) {
if (i == 0) // 這裏的兩個出口和調換了一下順序(與筆記想筆記),因爲比如當數組只有一個元素arr[] = {4}, S = 5,此時因該返回False纔對。
return (arr[0] == S);
else if(S == 0)
return 1;
else
return RecSubset(arr, i-1, S-arr[i]) || RecSubset(arr, i-1, S);
}
/**
* 非遞歸形式:動態規劃
*/
int DPSubset(int arr[], int len, int S) {
for (int i = 0; i < len; i++) { // 將S == 0 列都初始化爲True
subset[i][0] = 1;
}
for (int s = 0; s <= S; s++) { // 將 i == 0 行,除了arr[0]列初始化爲True,其餘都初始化爲False
subset[0][s] = 0;
}
subset[0][arr[0]] = 1;
for (int i = 1; i < len; i++) {
for (int s = 1; s <= S; s++) {
if (arr[i] > s)
subset[i][s] = subset[i-1][s];
else {
subset[i][s] = (subset[i-1][s-arr[i]] || subset[i-1][s]);
}
}
}
return subset[len-1][S];
}
int main() {
if (RecSubset(arr, 5, 13))
printf("True\n");
else
printf("False\n");
if (DPSubset(arr, 6, 13))
printf("True\n");
else
printf("False\n");
return 0;
}
Todo
- 01揹包
- 完全揹包
- 多重揹包