思维导图:
基本概念
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划(DP)。
基本思想与策略
将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
与分治法的区别
适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
基本要素
1.最优子结构
2.重叠子问题
动态规划的算法设计
1:找出最优解的性质,并描述其结构特征
2:递归定义最优值
3:以自底向上的方式计算最优值
4:根据计算最优值时得到的信息构造出最优解
经典应用范例
矩阵连乘问题
基本思路:
设计算A[i:j],1≤i≤j≤n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]。
当i=j时,A[i:j]=Ai,因此,m[i][i]=0,i=1,2,…,n
当i<j时,若A[i:j]的最优次序在Ak和Ak+1之间断开,i<=k<j,则:m[i][j]=m[i][k]+m[k+1][j]+pi-1pkpj。由于在计算是并不知道断开点k的位置,所以k还未定。不过k的位置只有j-i个可能。因此,k是这j-i个位置使计算量达到最小的那个位置。
状态转移方程:
代码如下:
#include<iostream>
#define MAXN 10010
using namespace std;
void insertT(int* p,int n,int **m,int **s){
for(int i=1;i<=n;i++)
m[i][i]=0;
for(int r=2;r<=n;r++)
for(int i=1;i<=n-r+1;i++){
int j=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++){
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j]){
m[i][j]=t;
s[i][j]=k;
}
}
}
}
void trace(int i,int j,int **s){
if(i==j){
cout<<"A"<<i;
}
else{
cout<<"(";
trace(i,s[i][j],s);
trace(s[i][j]+1,j,s);
cout<<")";
}
}
int main(){
int n;
int M;
cin>>M;
while(M--){
int c=0;
int p[MAXN];
while(cin>>n){
p[c]=n;
c++;
if(cin.get()=='\n') break;
}
int **m=new int*[c];
m[0]=new int[(c+1)*(c+1)];
for(int i=1;i<c+1;i++){
m[i]=m[i-1]+c+1;
}
int **s=new int*[c];
s[0]=new int[(c)*(c)];
for(int i=1;i<c;i++){
s[i]=s[i-1]+c;
}
insertT(p,c-1,m,s);
cout<<m[1][c-1]<<endl;
trace(1,c-1,s);
}
}
最长公共子序列
基本思路:
具有最优子结构性质,分解成若干个小问题,其特征可以刻画为:
假如S1的最后一个元素与S2的最后一个元素相等,那么S1和S2的LCS就等于 {S1减去最后一个元素} 与 {S2减去最后一个元素} 的 LCS 再加上 S1和S2相等的最后一个元素。
假如S1的最后一个元素与S2的最后一个元素不等(本例子就是属于这种情况),那么S1和S2的LCS就等于 : {S1减去最后一个元素} 与 S2 的LCS, {S2减去最后一个元素} 与 S1 的LCS 中的最大的那个序列。
状态转移方程:
用c[i,j]表示Xi 和 Yj 的LCS的长度。其中X = {x1 ... xm},Y ={y1...yn},Xi = {x1 ... xi},Yj={y1... yj}。
代码如下:
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int cnt;
void DPG(int **c,const string s1,const string s2){
int l1=s1.length();
int l2=s2.length();
for(int i=0;i<=l1;i++){
c[i][0]=0;
}
for(int i=0;i<=l2;i++){
c[0][i]=0;
}
for(int i=1;i<=l1;i++)
for(int j=1;j<=l2;j++){
if(s1[i-1]==s2[j-1]) c[i][j]=c[i-1][j-1]+1;
else c[i][j]=max(c[i-1][j],c[i][j-1]);
}
}
void trace(int **c,int i,int j,const string s1){
if(i==0||j==0) return;
if(c[i][j]==c[i-1][j-1]+1){
trace(c,i-1,j-1,s1);
cout<<s1[i-1];
cnt++;
}
else if(c[i-1][j]==c[i][j]){
trace(c,i-1,j,s1);
}
else if(c[i][j-1]<c[i][j]){
trace(c,i,j-1,s1);
}
}
int main(){
int m;
cin>>m;
while(m--){
cnt=0;
string s1,s2;
cin>>s1>>s2;
int l1=s1.length();
int l2=s2.length();
int **c=new int*[l1+1];
c[0]=new int[(l2+1)*(l2+1)];
for(int i=1;i<l1+1;i++){
c[i]=c[i-1]+l2+1;
}
DPG(c,s1,s2);
cout<<"最优解: ";
trace(c,l1,l2,s1);
cout<<endl<<"最优解的值: "<<cnt<<endl;
}
}
最大子段和
基本思想:
设dp[i]为以i结尾的最大子段和,那对于dp[i]而言只有两种情况,如果dp[i - 1] > 0, 那么dp[i] = dp[i - 1] + a[i];不然,dp[i] = a[i],然后求出dp数组中的最大值即可。
图像压缩
基本思想:
图像压缩问题就是要确定像素序列{p1,p1,……pn}的最优分段,使得依此分段所需的存储空间最小。
设s[i],1<=i<=n是像素序列{p1,p1,……pi}的最优分段所需的存储位数,
则S[i]为 S[i]=S[i-1]+1*log(Pi)+11 或 S[i]=S[i-2]+2*log(max(Pi-1,Pi))+11 或S[i]=S[i-3]=log(max(Pi,Pi-1,Pi-2))+11… 或S[i]=S[0]+i*log(max(Pi,Pi-1…,P1))+11中的最小值,可归纳为下面式子:
则s[i]为前i-k个的存储位数加上后k个的存储空间。由最优子结构性质可得:
0-1背包问题
基本思想:
满足最优子结构
令c[i][j]表示第1个商品到第i个商品中,背包容量为j的情况下,可获得的最大价值;
决定是否选择商品i的方案,比较选与不选的获得的价值
状态转移方程:
i=0或j=0时 F[i][j]=0
F[i][j]为背包容量为j时,可选择i件物品(选或不选)的总结价值最优值
代码如下:
#include<iostream>
#include<fstream>
using namespace std;
int item[10010];
void DPB(int **F,int *w,int *v,int N,int W){
for(int i=0;i<=N;i++){
F[i][0]=0;
}
for(int i=0;i<=W;i++){
F[0][i]=0;
}
for(int i=1;i<=N;i++)
for(int j=1;j<=W;j++){
if(j<w[i])
F[i][j]=F[i-1][j];
else
F[i][j]=max(F[i-1][j],F[i-1][j-w[i]]+v[i]);
}
}
void trace(int **F,int *w,int *v,int i,int j){
if(i==0||j==0) return ;
if(F[i][j]==F[i-1][j]){
item[i]=0;
trace(F,w,v,i-1,j);
}
else if((j-w[i])>=0&&F[i][j]==(F[i-1][j-w[i]]+v[i])){
item[i]=1;
trace(F,w,v,i-1,j-w[i]);
}
else return ;
}
int main(){
ifstream fin;
fin.open("input.txt");
if(!fin.is_open()){
cout<<"ERROR";
return 0;
}
int N,W;
fin>>N>>W;
int v[N+1];
int w[N+1];
for(int i=1;i<=N;i++)
fin>>v[i];
for(int i=1;i<=N;i++)
fin>>w[i];
int **F=new int*[N+1];
for(int i=0;i<=N;i++)
F[i]=new int[W+1];
DPB(F,w,v,N,W);
fstream fout;
fout.open("output.txt");
fout<<F[N][W]<<endl;
trace(F,w,v,N,W);
for(int i=1;i<=N;i++)
fout<<item[i]<<" ";
fin.close();
fout.close();
}
最优二叉树搜索树
上代码:
#include <iostream>
using namespace std;
const int MaxVal = 9999;
const int n = 5;
//搜索到根节点和虚拟键的概率
double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2};
double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1};
int root[n + 1][n + 1];//记录根节点
double w[n + 2][n + 2];//子树概率总和
double e[n + 2][n + 2];//子树期望代价
void optimalBST(double *p,double *q,int n)
{
//初始化只包括虚拟键的子树
for (int i = 1;i <= n + 1;++i)
{
w[i][i - 1] = q[i - 1];
e[i][i - 1] = q[i - 1];
}
//由下到上,由左到右逐步计算
for (int len = 1;len <= n;++len)
{
for (int i = 1;i <= n - len + 1;++i)
{
int j = i + len - 1;
e[i][j] = MaxVal;
w[i][j] = w[i][j - 1] + p[j] + q[j];
//求取最小代价的子树的根
for (int k = i;k <= j;++k)
{
double temp = e[i][k - 1] + e[k + 1][j] + w[i][j];
if (temp < e[i][j])
{
e[i][j] = temp;
root[i][j] = k;
}
}
}
}
}
//输出最优二叉查找树所有子树的根
void printRoot()
{
cout << "各子树的根:" << endl;
for (int i = 1;i <= n;++i)
{
for (int j = 1;j <= n;++j)
{
cout << root[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
//打印最优二叉查找树的结构
//打印出[i,j]子树,它是根r的左子树和右子树
void printOptimalBST(int i,int j,int r)
{
int rootChild = root[i][j];//子树根节点
if (rootChild == root[1][n])
{
//输出整棵树的根
cout << "k" << rootChild << "是根" << endl;
printOptimalBST(i,rootChild - 1,rootChild);
printOptimalBST(rootChild + 1,j,rootChild);
return;
}
if (j < i - 1)
{
return;
}
else if (j == i - 1)//遇到虚拟键
{
if (j < r)
{
cout << "d" << j << "是" << "k" << r << "的左孩子" << endl;
}
else
cout << "d" << j << "是" << "k" << r << "的右孩子" << endl;
return;
}
else//遇到内部结点
{
if (rootChild < r)
{
cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl;
}
else
cout << "k" << rootChild << "是" << "k" << r << "的右孩子" << endl;
}
printOptimalBST(i,rootChild - 1,rootChild);
printOptimalBST(rootChild + 1,j,rootChild);
}
int main()
{
optimalBST(p,q,n);
printRoot();
cout << "最优二叉树结构:" << endl;
printOptimalBST(1,n,-1);
}