第三章 动态规划思维导图

 思维导图:

基本概念

       每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划(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);  
    }

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值