在
bilibili
看到一个博主讲的比较好的动态规划内容,自己对应整理了一下学习笔记,以便巩固学习
代码简单实现
/**
* 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
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,因为有时间冲突。现在有一个员工,如何才能赚最多的钱,最多能赚几块钱?
从上往下分析,得出递推公式(选不选)
其中 O P T ( i ) OPT(i) OPT(i) 表示,当我要考虑前 i i i 个任务时,它的最优解是什么,即最多能赚几块钱?
例如:当考虑前8个任务时 O P T ( 8 ) OPT(8) OPT(8),对于任务8有两种状态,选和不选。
然后选这两个状态结果最大的,作为前8个任务的最优解 O P T ( 8 ) OPT(8) OPT(8) ,即得到如下递归公式:
O P T ( 8 ) = m a x { 4 + O P T ( 5 ) , 选 任 务 8 O P T ( 7 ) , 不 选 任 务 8 OPT(8) = max \left\{ \begin{aligned} && 4+ OPT(5) \ \ \ \ &, 选任务8 \\ && OPT(7) \ \ \ \ &,不选任务8 \end{aligned}\right. OPT(8)=max{4+OPT(5) OPT(7) ,选任务8,不选任务8
整理一下递推公式:
O P T ( i ) = m a x { v i + O P T ( p r e v ( i ) ) , 选 任 务 i O P T ( i − 1 ) , 不 选 任 务 i OPT(i) = max \left\{ \begin{aligned} && v_i+ OPT(prev(i)) \ \ \ \ &, 选任务i \\ && OPT(i-1) \ \ \ \ &,不选任务i \end{aligned}\right. OPT(i)=max{vi+OPT(prev(i)) OPT(i−1) ,选任务i,不选任务i
其中 v i v_i vi 表示第 i i i 个任务的奖励工资, p r e v ( i ) prev(i) prev(i) 表示如果要做第 i i i 个任务,则它前面顶多还能做前几个。例如: p r e v ( 8 ) = 5 prev(8) = 5 prev(8)=5 表示如果要做第8个任务,则前面顶多还能做前5个,即加上考虑前5个任务的最优解 O P T ( p r e v ( 8 ) ) OPT(prev(8)) OPT(prev(8))。
首先计算 p r e v prev prev 数组,比如 i = 1 i = 1 i=1 时, p r e v ( 1 ) = 0 prev(1) = 0 prev(1)=0 表示如果做任务1,则前面能做的为0个,以此类推。我们最终要算的是前面8个任务的最优解,即 O P T ( 8 ) OPT(8) OPT(8) ,根据递归式可以得到如上状结构,可以发现:(1)首先这是一个找最优解问题。(2)大问题的最优解依赖于子问题的最优解。(3)大问题分解成子问题,子问题间有重复子问题。 这样的问题就可以使用动态规划思想来做。从上往下分析问题,从下往上求解问题。从 O P T ( 1 ) OPT(1) OPT(1) 开始分析计算并保存对应结果。
O P T ( 1 ) = v 1 = 5 OPT(1) = v_1 = 5 OPT(1)=v1=5
O P T ( 2 ) OPT(2) OPT(2) 分析:如果不选任务2,则是 O P T ( 1 ) = 5 OPT(1) = 5 OPT(1)=5 ;如果选任务2,则是 O P T ( p r e v ( 2 ) ) + 任 务 2 价 值 = 0 + 1 OPT(prev(2)) + 任务2价值 = 0 + 1 OPT(prev(2))+任务2价值=0+1 。最后选这两个状态的最大值作为前个任务2的最优解,所以 O P T ( 2 ) = 5 OPT(2) = 5 OPT(2)=5。表示如果只有前面两个任务的时候,最多能挣5块钱。
O P T ( 4 ) OPT(4) OPT(4) 分析:如果不选任务4,则是 O P T ( 3 ) = 8 OPT(3) = 8 OPT(3)=8 ;如果选任务4,则是 O P T ( p r e v ( 4 ) ) + 任 务 4 价 值 = O P T ( 1 ) + v 4 = 5 + 4 = 9 OPT(prev(4)) + 任务4价值= OPT(1) + v_4 = 5 + 4 = 9 OPT(prev(4))+任务4价值=OPT(1)+v4=5+4=9 。最后选这两个状态的最大值作为前4个任务的最优解,所以 O P T ( 4 ) = 9 OPT(4) = 9 OPT(4)=9。表示如果只有前面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
#include
#include
#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;
}
在如下一堆数字中选出一些数字,如何让数字之和最大?
限定条件:选出的数字不能是相邻的。
从上往下分析,得出递归公式(选不选)
O P T ( 6 ) OPT(6) OPT(6) :表示到下标为6 这个位置的最佳方案(最优解)是什么?
从上往下开始分析:对于 O P T ( 6 ) OPT(6) OPT(6) 我们有两种状态,选3,和不选3。即有如上递归式。
然后选这两个状态结果最大的,作为到下标为6这个位置的最优解 O P T ( 6 ) OPT(6) OPT(6)
进而,我们可以得到这样的递归方程:
O P T ( i ) = m a x { O P T ( i − 2 ) + a r r [ i ] , 选 a r r [ i ] O P T ( i − 1 ) , 不 选 a r r [ i ] OPT(i) = max \left\{ \begin{aligned} && OPT(i-2) + arr[i] \ \ \ \ &, 选arr[i] \\ && OPT(i-1) \ \ \ \ &,不选arr[i] \end{aligned}\right. OPT(i)=max{OPT(i−2)+arr[i] OPT(i−1) ,选arr[i],不选arr[i]
分析递归出口
O P T ( 0 ) = a r r [ 0 ] O P T ( 1 ) = m a x ( a r r [ 0 ] , a r r [ 1 ] ) OPT(0) = arr[0] \\ OPT(1) = max(arr[0], arr[1]) OPT(0)=arr[0]OPT(1)=max(arr[0],arr[1])
代码简单实现
/**
* 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
#include
#include
#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;
}
在如下一堆数字中选出一些数字求和,使其等于数字 S S S。如果存在这样的方案则返回 t r u e true true,否则返回 f a l s e false false。
从上往下分析,得出递归公式(选不选)
分析:我们定义 S u b s e t Subset Subset 表示子集, S u b s e t ( a r r [ 5 ] , 9 ) Subset(arr[5], 9) Subset(arr[5],9) 表示对前面5个数字取数求和为9,应该怎么分配。从上往下分析,当 i = 5 i = 5 i=5 时,我们有两种状态:
最后只要这两种方案有一个为 t r u e true true,则满足要求,所以中间用 o r or or
分析递归出口
动态规划解法
即非递归方式,我们需要借助二维数组,保存我们的中间过程(中间的这些重叠子问题)。如下设计我们的二维数组:
其中,最左边列是 a r r [ ] arr[ ] arr[] 数组,右边是设计一个二维数组,行坐标表示下标 i i i ,纵坐标表示 S S S ,比如图中的绿色方框表示 S u b s e t ( a r r [ 2 ] , 3 ) Subset(arr[2], 3) Subset(arr[2],3) 此时,由于 a [ 2 ] = 4 > S = 3 a[2] = 4 > S = 3 a[2]=4>S=3 ,所以只能不选 a r r [ 2 ] arr[2] arr[2] ,即得到当前方案 S u b s e t ( a r r [ 1 ] , 3 ) Subset(arr[1], 3) Subset(arr[1],3) 。明白含义之后,我们来定义我们的出口条件:
然后按照从左往右的顺序,把每一行填好,最后返回最后一个结果即可。
对于 s u b s e t [ 0 , 0 ] subset[0, 0] subset[0,0] 我们规定为 F a l s e False False
代码简单实现
/**
* 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
#include
#include
#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;
}