动态规划:
核 心 :重叠子问题,最优子结构
解题要点:递归函数,递归出口
适用范围:最优解,最大值
1.例题引入:斐波那契数列
使用递归解法:
public static int Fibonacci(int n) {
if(n <= 1){
return n;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
当n=4时,
Fibonacci(4) = Fibonacci(3) + Fibonacci(2)
= Fibonacci(2) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0)
= Fibonacci(1) + Fibonacci(0) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0);
重叠子问题:由于我们的代码并没有记录Fibonacci(1)和Fibonacci(0)的结果,对于程序来说它每次递归都是未知的,因此光是n=4时Fibonacci(1)就重复计算了3次之多。
动态规划方法:
public static int Fibonacci(int m) { //第m项
int i=m-1;//第m项的下标
int opt[]=new int[m];
opt[0]=1; //递归出口
opt[1]=1;
for(i=2;i<opt.length;i++) { //递归
opt[i]=opt[i-1]+opt[i-2];
}
return opt[opt.length-1];
}
2. 加权项目时间计划问题:
问题描述:
在指定的时间内使得收益最高
思路:
递推式:OPT表示最优解
递归出口: opt[0]=0
代码实现:
public static int solution(int[] value,int[] prev,int i) {
int opt[]=new int[i];
opt[0]=0;
for(i=1;i<opt.length;i++) {
int A=value[i]+opt[prev[i]];
int B=opt[i-1];
opt[i]=(A>B)?A:B;
}
return opt[i-1];
}
3.例题一:
问题描述:
题目描述:从一组数据中挑出互不相邻的的数,使和最大,挑的数不限量。(例如1,4,7,3 可以,1,8,3不可以)
解题思路:
该题目中分解的子问题中存在重复项,故用DP效率更高
递推函数:
递归出口:
代码实现:
public static int rec_opt(int i,int[] arr) { //递归方法
if(i==0) return arr[i];
if(i==1) {
if(arr[0]>arr[1]) return arr[0];
else return arr[1];
}
int A=rec_opt(i-2,arr)+arr[i];
int B=rec_opt(i-1,arr);
int result=(A>B)? A:B;
return result;
}
public static int dp_opt(int[] arr) { //dp方法
int opt[]=new int[arr.length]; //opt[]用来存储最优解
opt[0]=arr[0];
opt[1]=(arr[0]>arr[1])?arr[0]:arr[1];
for(int i=2;i<arr.length;i++) {
int A=opt[i-2]+arr[i];
int B=opt[i-1];
opt[i]=(A>B)?A:B;
}
return opt[opt.length-1];
}
4.例题二:
问题描述:
给定一个数组arr和数字S,从arr中任意的选取值,使得值的和等于S,如果可以返回True 否则返回False
解题思路:
分析:建立子集Subset(arr[i],s)其中i表示当前的位置,s表示求的和
例如subset(arr[5],9)就表示
递推式:
递归出口:
DP表:
代码实现:
#include<stdio.h>
#include<math.h>
int arr[]={3,34,4,12,5,2};
int len=6;
int s=9;
bool dp_subset(int arr[],int s)
{
bool subset[len][s+1];
int i,j;
for(i=0;i<len;i++)
for(j=0;j<s+1;j++)
subset[i][j]=false;
for(i=0;i<len;i++) //递归出口,第一列全为T
subset[i][0]=true;
for(j=0;j<s+1;j++) //递归出口,第一行全为F
subset[0][j]=false;
for(i=1;i<len;i++) //从左到右,逐行遍历二维数组
for(j=1;j<s+1;j++)
{
if (arr[i]>s){ //减枝
subset[i][j]=subset[i-1][j];
}else
{
subset[i][j]= (subset[i-1][j]) || (subset[i-1][j-arr[i]]) ;
}
}
return subset[len-1][s];
}
int main()
{
bool ans=dp_subset(arr,s);
if(ans){
printf("True");
}else{
printf("False");
}
return 0;
}
5. 01背包问题(knapsack)
问题描述:
题目描述 小偷带了一个容量为M(20)的包,然后小偷怎么偷上述东西使得价值最高
解题思路:
递推公式: K也可以理解还能偷几样东西(背包里还能放几样东西)
递归出口:1.背包大小为0,或者东西数量为0 2.剪枝:第K件物品太大
代码实现:
#include<stdio.h>
#define N 6
#define M 21
int B[N][M]={0};//保存每种背包形式的结果
int v[N]={0,3,4,5,8,10};
int w[N]={0,2,3,4,5,9};
//对于这种问题 要学好数学的递推公式 然后再抽象成程序语言 最简单的背包问题的算法
void knapsack()
{
int k,c;
for(k=1;k<N;k++){
for(c=1;c<M;c++){
if(w[k]>c)//当前的商品重量大于了背包容量
{
B[k][c]=B[k-1][c]; //c表示当前背包的剩余容量 ,k表示还可以偷的个数
}else{
int v1=B[k-1][c-w[k]]+v[k]; //偷的话
int v2=B[k-1][c]; //不偷的话
if(v1>v2){
B[k][c]=v1;
}else{
B[k][c]=v2;
}
}
}
}
}
int main()
{
knapsack();
printf("***%d****\n",B[5][20]);
return 0;
}
6.最长公共子序列(LCS)
问题描述:
给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。
比如字符串1:BDCABA;字符串2:ABCBDAB则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA
解题思路:
LCS问题的递推公式
c[i,j]表示:(x1,x2…xi) 和 (y1,y2…yj) 的最长公共子序列的长度。(是长度哦,就是一个整数嘛)
代码实现:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1000;
char a[N],b[N];
int dp[N][N];
int main()
{
int lena,lenb,i,j;
while(scanf("%s%s",a,b)!=EOF)
{
memset(dp,0,sizeof(dp));//设置了递归出口
lena=strlen(a);
lenb=strlen(b);
for(i=1;i<=lena;i++)
{
for(j=1;j<=lenb;j++)
{
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d\n",dp[lena][lenb]);
}
return 0;
}
相关链接:
https://blog.csdn.net/qq_41910103/article/details/94054611#__118