动态规划初步

4月的最后一天,我4月初就说过,这个月要写出6篇博文,现在就差这篇了。

动态规划是一个比较复杂的算法,最近一直在学习,0-1背包问题属于其中经典中的经典了,现在我们一起重温经典,并谈谈我的理解。

在切入背包问题之前,我们首先要对以下几个概念有所理解:

最优子结构:即全局最优解包含局部最优解。

重叠子问题:在涉及到递归的时候,会遇到相同的子问题被重复计算了多次,可以采用记忆化搜索与递推解决。

那好,我首先通过一个例子引入动态规划。

有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,如图所示:

从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使这个和尽量大?

首先想到的一定是递归,我们先采用最普通的方法对其求解。

方法一

这里定义状态指标函数d(i,j)为以格子(i,j)为终点能得到的最大和。

int d(int i ,int j)
{
    if(i == 1)
    {
        return a[1][1]; 
    }else if(j == 1)  //注意这种特殊情况
    {
        return a[i][j] + d(i-1,j);
    }
    else{
        return a[i][j] + (d(i-1,j-1) > d(i-1,j) ? d(i-1,j-1) : d(i-1,j));
    }
}

当然你也可以定义d(i,j)为以格子(i,j)为起点能得到的最大和。

这样做是正确的,但时间效率太低,其原因在于重复计算,也就是我之前提到的重复子问题。具体而言,我用以下图示说明。



我要计算d(4,2)对应的要调用的关系树如上所示,显然d(1,1)被计算了3遍,d(2,1)被计算了两遍,也许你会认为重复计算一两个数没什么大不了的,但事实是:这样的重复不是单个节点,而是一颗子树


记忆化搜索与递推

我们注意到,之所以会出现上述的重复,原因在于之前已经被计算出的值没有被保存而导致重新计算了。

因此我们可以通过简单地赋值操作解决上述问题。

int d(int i ,int j)
{
    if(d([i][j] >= 0)
    return d[i][j];
    else
    {
        if(i == 1)
        {
            return d[i][j] = a[1][1]; 
        }else if(j == 1)  //注意这种特殊情况
        {
            return d[i][j] a[i][j] + d(i-1,j);
        }
        else{
            return d[i][j] = a[i][j] + (d(i-1,j-1) > d(i-1,j) ? d(i-1,j-1) : d(i-1,j));
        }
    }
}

上述程序依然是递归的,但同时计算结果保存在数组d中,题目中说各个书都是非负的,因此如果计算出某个d[i][j],则他应该是非负的,这样只需把所有d初始化-1,即可通过的d[i][j]>=0,得知它是否被计算过。



猜你喜欢

转载自blog.csdn.net/u011995233/article/details/24772255
今日推荐