“动态规划算法”那些事儿

最近研究网上的各种面经,看到有很多公司的面试题都是关于动态规划的,所以想总结一下自己对于动态规划的理解。我一直非常仰慕那些参加过ACM/ICPC的牛人,他们往往能够很快就能对题目做出分析,并给出应对的算法。笔者曾经做过一些ACM的题目,里面动态规划的题目也不在少数,而且ACM的题目有个特点:很多时候你认为是一道图论题的时候他恰恰是一道要用动态规划来解答的题目,就像东野圭吾的小说「嫌疑犯X的献身」中数学家石神出的数学题一样,表面上是几何问题其实是函数问题。蛋有点扯远了,我只想告诉大家,相比背诵问题的答案,分析问题的方法才是最重要的。 在介绍动态规划之前,先看一个大家很熟悉的例子。

斐波那契数列的递归定义: F0 = 0 F1 = 1 Fn = Fn-1 + Fn-2 很容易可以写出代码:

int fibonacci(int n)
{
    if(n==0) return 0;
    else if(n==1) return 1;
    else return fibonacci(n-1)+fibonacci(n-2);
}

但是递归的效率并不高,如果画出递归树,会发现很多值被重复计算。一个好的想法是把已计算的结果保存下来,以后做重复任务的时候直接可以获取,以减少不必要的递归。改进以后的代码如下:

#include <iostream>
#include <memory.h>
#define N 100
using namespace std;

int array[N];

int fibonacci(int n)
{
    if(array[n]!=-1) return array[n];
    if(n==0)return 0;
    else if(n==1) return 1;
    else
    {
        array[n]=fibonacci(n-1)+fibonacci(n-2);
        return array[n];
    }
}

int main()
{
    memset(array,-1,sizeof(array));//初始化备忘数组为-1
    cout << fibonacci(8);
    return 0;
}

所以在我看来动态规划的基本思想就是保存递归时的结果,因而不会在解决同样的问题时花费时间 。动态规划通常用于求解最优化问题,他同分治算法类似,都是将待求解的问题分解为若干子问题,先求解子问题然后得出原问题的解。与分治不同之处在于分治算法各个子问题是互相独立的而动态规划类的子问题往往是相互联系的。 同样是求解最优化问题的贪心算法与动态规划的区别在于贪心算法追求每一步都达到最优,而动态规划则是追求整体的最优。 要使用动态规划算法必须满足最优化原理和无后效性。最优化原理就是:一个最优化策略的子策略总是最优的。而无后效性简而言之就是当前的决策与过去的状态无关。 使用动态规划算法的步骤:(1)分析最优解的性质,并刻画其结构特征。(2)递归定义最优值。(3)以自底向上或者自顶向下的记忆方法计算最优值。 动态规划的有一些经典题目:背包问题、矩阵链乘、最长公共子序列……可以拿来做一下,加深自己的印象。

【参考文献】ACM程序设计竞赛基础教程

王小兵 /
Published under (CC) BY-NC-SA in categories ACM算法  tagged with ACM