動態規劃:
核 心 :重疊子問題,最優子結構
解題要點:遞歸函數,遞歸出口
適用範圍:最優解,最大值
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