算法小结--动态规划(一)

用一个故事教会我动态规划:(转载)查看这里


实例一:Fibonacci序列
F(n) = F(n-1) + F(n-2),F(0) = 1;f(1) = 1;

  1. 递归算法:
int fuc(int n){
    if(n==0 || n==1 ) return 1;
    else return fuc(n-1) + fuc(n-2);
}

该简单的递归算法具有指数级的算法复杂度;因为在递归运算中存在重复运算的问题,比如计算F(5),先求出F(3)+F(4),然后要求出F(2)+F(1)和F(3)+F(2),然后从后面一次返回。我们可以看出F(3)重复计算了两次。

  1. 改进后的递归
int rec[20]; //记录数组,将F(n)的值保存起来。 

int fuc(int n){
    if(rec[n]>0) return rec[n]; //如果rec[n]保存了f(n)的值,不需要重复计算,直接返回。 
    if(n==0 || n==1 ) rec[n] = 1;
    else rec[n] = fuc(n-1) + fuc(n-2);
    return rec[n];
}

虽然该方法避免了大量的重复计算,但是存在大量的函数调用,每次都需要花费大量的时间用于参数的传递和动态链接等。
事实上,我们可以使用内存换时间,计算F(n) ,只需要知道 F(n-1) 和F(n-2),所以我们可以从迭代出口开始,利用迭代算法。

  • 迭代算法:
int fuc(int n){
    int i,f[20];
    f[0] = 1;
    f[1] = 1;
    for(i = 2;i<=n;i++){
        f[i] = f[i-1]+f[i-2];
    }
    return f[n];
}

这个就是最简单的动态规划算法,时间复杂度O(n);

实例二:01背包问题
有n个重量和价值分别为W[i],V[i]的物品。从这些物品中选出总重量不超过Weight的物品,求所有方案中价值总和的最大值。
分析:我最先想到的是进行暴力搜索,把所有情况都计算一遍,然后得出最大值。
代码如下:

int n = 4; //数量 
int w[4] = {2,1,3,2}; //每个物品的重量 
int value[4] = {3,2,4,2}; //,每个物品的价值 
int Weight = 5; //总重量 

int max(int a,int b){
    return a>b?a:b;
}

int fuc(int W , int k){
    int res;
    if(k == n) res = 0; //注意 k是从0开始,当k等于4的时候,已经没有物品了,w[4]已经超界了。 
    else if(W>=w[k]){                                        //当选择某个物品的时候,剩余总重量大于当前物品的总重量
        res = max( fuc(W,k+1),fuc(W-w[k],k+1) + value[k] );  //有两个选择,选择当前物品,或者后面有性价比更高的物品, 
    }else{                                                   //放弃当前物品 。 
        res = fuc(W,k+1);
    }
    return res;
}

int main() {
    printf("%d\n",fuc(5,0));
    return 0;
}

可以看出这个递归的搜索方法的深度是n,而且每次都有两次分支,可知时间的复杂度是O(2^n)。从实例一可以知道,递归往往存在着重复计算,本题也不例外。所以我们再次考虑使用记忆化数组保存中间结果,提高算法效率。
代码如下:

#include <stdio.h>
#include <stdlib.h>

//int f[20]; //记录数组,将F(n)的值保存起来。 

int n = 4; //数量 
int w[4] = {2,1,3,2}; //每个物品的重量 
int value[4] = {3,2,4,2}; //,每个物品的价值 
int Weight = 5; //总重量 
int rec[5][5]; //记忆化数组 

int max(int a,int b){
    return a>b?a:b;
}

int fuc(int W , int k){
    int res;
    if(rec[W][k]>0) return rec[W][k]; //如果保存了fuc(W,k)的值就直接返回。 
    if(k == n) res = 0; //注意 k是从0开始,当k等于4的时候,已经没有物品了,w[4]已经超界了。 
    else if(W>=w[k]){                                        //当选择某个物品的时候,剩余总重量大于当前物品的总重量
        res = max( fuc(W,k+1),fuc(W-w[k],k+1) + value[k] );  //有两个选择,选择当前物品,或者后面有性价比更高的物品, 
    }else{                                                   //放弃当前物品 。 
        res = fuc(W,k+1);
    }

    return rec[W][k] = res;
}

int main() {
    printf("%d\n",fuc(5,0));
    return 0;
}

这种算法大幅度提高了效率。
Fibonacci序列问题存在迭代式,01背包问题同样存在迭代式
dp[W][i]表示剩余重量为W,选取物品编号为i的最大价值数。
迭代式:
dp[W][n] = 0;当选到第n个时,已经没有物品可选
dp[W][i] = dp[W][i+1] ( W < w[i]); 当剩余重量小于当前物品重量时,不能选取。
dp[W][i] = max( dp[W][i+1],dp[W-w[i] ][i+1] + value[i] ) (W>=w[i]); 分为两种选取或不选取,求其最大值。
代码如下:

int n = 4; //数量 
int w[4] = {2,1,3,2}; //每个物品的重量 
int value[4] = {3,2,4,2}; //,每个物品的价值 
int Weight = 5; //总重量 
int dp[5][6]; //保存物品价值和 

int max(int a,int b){
    return a>b?a:b;
}

int fuc(int W , int k){
    int i,j;
    for(i=k-1;i>=0;i--){
        for(j=0;j<=W;j++){
            if(j<w[i]){
                dp[i][j] = dp[i+1][j];
            }else{
                dp[i][j] = max( dp[i+1][j], dp[i+1][ j - w[i] ] + value[i] );
            }
        }
    }
    return dp[0][W];
}

int main() {
    printf("%d\n",fuc(5,4));
    return 0;
}

比较以上两种方法;时间复杂度相同O(n*w);记忆化递归方法实现比较容易,很多问题找迭代式很难,但是迭代式的动态规划代码相对简断。


未完待续

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值