动态规划与记忆化搜索

52 篇文章 0 订阅
24 篇文章 1 订阅

一. 动态规划

动态规划(dynamic programming),与“分治思想”有些相似,都是利用将问题分 为子问题,并通过合并子问题的解来获得整个问题的解。于“分治”的不同之处在 于,对于一个相同的子问题动态规划算法不会计算第二次,其实现原理是将每一个计算过的子问题的值保存在一个表中。

二. 记忆化搜索

我们常见的动态规划问题,比如流水线调度问题,矩阵链乘问题等等都是“一步接着一步解决的”,即规模为 i 的问题需要基于规模 i-1 的问题进行最优解选择,通常的递归模式为DP(i)=optimal{DP(i-1)}。而记忆化搜索本质上也是DP思想,当子问题A和子问题B存在子子问题C时,如果子子问题C的最优解已经被求出,那么子问题A或者是B只需要“查表”获得C的解,而不需要再算一遍C。记忆化搜索的DP模式比普通模式要“随意一些”,通常为DP(i)=optimal(DP(j)), j < i。

三. 滑雪问题

上图显示为R*C的雪场,R是行数,C是列数。圆圈内的数字表示的是雪场的海拔高度h,根据常识我们知道,滑雪只有从上往下滑行才可能滑的动,现在我们想要求出能够滑行的最长距离,上面的例子我们可以很直接判断出25-24-......-1这个顺时针方向螺旋的滑雪方式可以滑的最远。

那么这个问题如何用编程来实现呢?我们发现这是一个典型的递推,DP(i, j)表示从坐标(i,j)出发所能滑行的最大长度,且有:DP(i, j)=optimal{DP(i±1, j±1)}+1。下面貼上源代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int max_size=110;
int R,C;
int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
int h[max_size][max_size],dp[max_size][max_size];
int inMap(int x,int y){
    if(x>=0&&x<=R-1&&y>=0&&y<=C-1) return 1;
    return 0;
}
int max2(int a,int b,int c,int d){
    return max(max(a,b),max(c,d));
}
int dfs(int i,int j){
    int nx,ny,down=0,up=0,left=0,right=0;
    if(dp[i][j]) return dp[i][j];
    nx=i+dir[0][0]; ny=j+dir[0][1];
    if(inMap(nx,ny)){
        if(h[i][j]>h[nx][ny]) up=dfs(nx,ny);
    }
    nx=i+dir[1][0]; ny=j+dir[1][1];
    if(inMap(nx,ny)){
        if(h[i][j]>h[nx][ny]) right=dfs(nx,ny);
    }
    nx=i+dir[2][0]; ny=j+dir[2][1];
    if(inMap(nx,ny)){
        if(h[i][j]>h[nx][ny]) down=dfs(nx,ny);
    }
    nx=i+dir[3][0]; ny=j+dir[3][1];
    if(inMap(nx,ny)){
        if(h[i][j]>h[nx][ny]) left=dfs(nx,ny);
    }
    dp[i][j]=max2(up,down,left,right)+1;
    return dp[i][j];
}
int main(){
    scanf("%d%d",&R,&C);
    memset(h,0,sizeof(h));
    memset(dp,0,sizeof(dp));
    for(int i=0;i<R;i++){
        for(int j=0;j<C;j++){
            scanf("%d",&h[i][j]);
        }
    }
    int ans=-1;
    for(int i=0;i<R;i++){
        for(int j=0;j<C;j++){
            ans=max(ans,dfs(i,j));
        }
    }
    printf("%d\n",ans);
}

另一种:

首先对所有元素排序,设为a1,a2,a3 ,a4,a5,a6….

然后按照升序顺序逐次求每个元素ax的最大长度,针对每个元素对上下左右四个方向的元素与元素ax的高度比较,如果比ax大,放弃,如果比ax小,则ax经过此元素形成长度加一的最长路径。

#include <iostream>
#include <cstdlib>
using namespace std;
/*
*记录高度值(用于排序)
*记录位置row col用于寻找周围元素
*/
struct element{
    int data;
    int row;
    int col;
};
/*
*记录高度值和经过此点的最大长度
*/
struct arrnode{
    int high;
    int mlen;
};
/*
*快排比较函数
*/
int compare( const void * ele1,const void * ele2){
    element *ele11 = (element *)ele1;
    element *ele22 = (element *)ele2;
    if ( ele11->data< ele22->data )
        return -1;
    else if( ele11->data ==ele22->data )
        return 0;
    else
        return 1;
}
/*
*分别从上下左右四个方向查看与row行col列元素x相邻的四个元素
*在四个元素中找高度值小于x并且长度最大的长度赋值给x的长度
*/
int Around_Max_length(int row, int col, arrnode * arr,int arrrow, int arrcol){
    int max = 1;
    int value = (*(arr+row*arrcol+col)).high;
    int tvalue = 0;
    int tlen = 0;
    if( (col - 1) >= 0 )
    {
        tvalue = (*(arr+row*arrcol+col-1)).high;
        tlen =  (*(arr+row*arrcol+col-1)).mlen;
        if( value > tvalue)
            max <(tlen+1) ? max=(tlen+1) : max = max;
    }
    if( (row - 1) >= 0 )
    {
        tvalue = (*(arr+(row-1)*arrcol+col)).high;
        tlen =  (*(arr+(row-1)*arrcol+col)).mlen;
        if( value > tvalue)
            max <(tlen+1) ? max=(tlen+1) : max = max;
    }
    if( (row + 1) < arrrow )
    {
        tvalue = (*(arr+(row+1)*arrcol+col)).high;
        tlen =  (*(arr+(row+1)*arrcol+col)).mlen;
        if( value > tvalue)
            max <(tlen+1) ? max=(tlen+1) : max = max;
    }
    if( (col + 1) < arrcol )
    {
        tvalue = (*(arr+row*arrcol+col+1)).high;
        tlen =  (*(arr+row*arrcol+col+1)).mlen;
        if( value > tvalue)
            max <(tlen+1) ? max=(tlen+1) : max = max;
    }
    return max;
}
int main(){
    int r = 0 , c = 0 ;
    cin>>r>>c;
    arrnode * arr_high = new arrnode[r*c];
    element * listele = new element[r*c];
    int listnum = 0;
    //初始化
    for ( int i = 0; i < r;i++ )
    {
        for ( int j = 0; j < c;j++ )
        {
            int t = 0;
            cin>>t;
            arrnode arn = {t,1};
            arr_high[listnum]= arn;
            element elet = {t,i,j};
            listele[listnum++]= elet;
        }
    }
    //快排
    qsort(listele,r*c,sizeof(element),compare );
    //对有序数组listele的每个元素依次计算其最大长度记录于arr_high用于以后计算的子问题的解
    for(int k = 0; k < r*c;k++ )
    {
        element fmlele =listele[k];
        int row = fmlele.row;
        int col = fmlele.col;
        arr_high[row*c+col].mlen= Around_Max_length(row,col,arr_high,r,c);
    }
    //遍历数组arr_high寻找最大长度
    int max = 0;
    for(int m = 0; m < r*c;m++ )
    {
        max <arr_high[m].mlen ? max = arr_high[m].mlen : max=max;
    }
    cout<<max<<endl;
    int kk;
    cin>>kk;
    return 0;
}

四. 切棒子问题

给你一根长n英尺的棒子和一份关于该棒子的价目表如下(其中 i = 1,2,3,…,n),请问如何将这根棒子卖出最高的价格,可以对棒子进行切割。

这个题同样是可以利用DP记忆化搜索来实现的,递推公式为DP(n)=optimal{max{price(i)+DP(n-i)|1≤i≤n}}。实现代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int max_size=50;
const int inf=1<<30;
int price[max_size],dp[max_size];
int n;
int dfs(int n){
    if(dp[n]) return dp[n];
    if(n==0) return 0;
    int mmax=-inf;
    for(int i=1;i<=n;i++){
        mmax=max(mmax,price[i]+dfs(n-i));
    }
    dp[n]=mmax;
    return dp[n];
}
int main(){
    while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=n;i++) scanf("%d",&price[i]);
        printf("%d\n",dfs(n));
    }
}

五. 01背包问题

问题描述: 有N件物品和一个重量为M的背包。(每种物品均只有一件)第i件物品的重量是w[i],价值是p[i]。求解将哪些物品装入背包可使价值总和最大。思路也很简单,直接看代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int max_size=50;
const int inf=1<<30;
int p[max_size],w[max_size],dp[max_size][max_size];
int n,v;
int dfs(int i,int v){
    if(dp[i][v]) return dp[i][v];
    if(i==0||v<=0) return 0;
    if(w[i]>v) dp[i][v]=dfs(i-1,v);
    else dp[i][v]=max(dfs(i-1,v),dfs(i-1,v-w[i])+p[i]);
    return dp[i][v];
}
int main(){
    while(scanf("%d",&n)!=EOF){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++) scanf("%d",&p[i]);
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        scanf("%d",&v);
        printf("%d\n",dfs(n,v));
    }
}

六. 总结

通过前两个例子分析,我们可以得出DP记忆化搜索的算法模板(自己DIY的,大家可以选择参考)

dfs(problem a){
    if(a has been solved) 
        then: consult the record.
    else//get the optimal solution of problem a.
        divide the problem a into several sub-problems(a1,a2,...,ak)
        get the solution of problem a by dfs(a1),dfs(a2),...,dfs(ak).
    finally write the optimal solution into record.
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值