我的算法笔记(6)动态规划1

动态规划

这里只是介绍基础的入门!

这里只是介绍基础的入门!

这里只是介绍基础的入门!

(毕竟博主现在也只是小白。。。)

引例 POJ 1163 数字三角形

问题:
  给定一个由n行数字组成的数字三角形,如下图所示:
   7
   3 8
   8 1 0
   2 7 4 4
   4 5 2 6 5

试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大(每一步只能从一个数走到下一层上和它最近的左边的数或者右边的数)。

输入:
  第一行是数字三角形的行数,接下来 n 行是数字三角形中的数字。

比如:

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

输出:

30

问题分析:

1.递归的思路

我们可以去写一个函数:F(i,j),其中表示从第i行,第j列的数字开始向下走,然后,因为需要找到从上到下的最大数字值,所以说,我们可以确认边界条件,那就是当i==N时,此时我们的递归走到了尽头,这个时候,最大的数字就只能是j,所以我们可以给出边界:

if(i==N)return a[i][j];//a是一个存放i行j列元素数字的数组

边界确定了,那么我们应该怎么去确定其中的变化过程呢?
显然,我们需要的是去寻找在俩条路中的最好的,数字和最大的路径,我们从a[i][j]向下有俩种走法:要么走a[i+1][j],要么走a[i+1][j],而我们选取走的路径的原则应该是,保证最终的数字和最大,所以,我们可以给出递归过程:

int sum1 = F(i+1,j);//sum1中存放的是沿着数字向正下的数字的和
    int sum2 = F(i+1,j+1);//存放的是向右下方的和
    if (sum1 > sum2)
        return sum1 + a[i][j];//那我就走sum1的路
    return sum2 + a[i][j];//走sum2的路

于是,我们就可以用递归的方法写出这个数字三角形的例题:

//POJ1163数字三角形_递归
#include<stdio.h>

int a[102][102];
int N;

int F(int i,int j)
{

    if(i==N)return a[i][j];
    int sum1 = F(i+1,j);//sum1中存放的是沿着数字向正下的数字的和
    int sum2 = F(i+1,j+1);//存放的是向右下方的和
    if (sum1 > sum2)
        return sum1 + a[i][j];//那我就走sum1的路
    return sum2 + a[i][j];//走sum2的路
}
main()
{

    int i,j;
    scanf("%d",&N);

    for (i=1;i<=N;i++)
    for (j=1;j<=i;j++)
    scanf("%d",&a[i][j]);
    printf("%d",F(1,1));

}

但是 ,**我们可以发现,当N达到一定程度后,程序将不能够算出结果,因为递归的层数太多了!**其实我们可以发现在递归的过程中,我们进行了很多不必要的重复运算:每次调用递归函数,都会使递归函数从头把数值给计算一遍(因为递归的结束是在每次i=N的时候),所以,我们能不能不去重复计算数字?
我们可以做一个数组,去记录数字的和,这样的话,我们就可以不必去每一遍的计算。
给出代码如图:

//7__POJ1163数字三角形_递归改进
#include<stdio.h>

int a[102][102];
int N;
int maxsum[110][100];
int F(int i,int j)
{

    if(i==N)return a[i][j];
   if(maxsum[i+1][j]==-1)//如果maxsum(r+1,j)没有计算过
        maxsum[i+1][j] = F(i+1,j);
    if(maxsum[i+1][j+1] == -1)//如果maxsum(r+1,j+1)没有计算过
        maxsum[i+1][j+1] = F(i+1,j+1);
    if (maxsum[i+1][j] > maxsum[i+1][j+1])
        return maxsum[i+1][j] + a[i][j];//那我就走sum1的路
    return maxsum[i+1][j+1] + a[i][j];//走sum2的路
}
main()
{

    int i,j;
    scanf("%d",&N);
    memset(maxsum,-1,sizeof(maxsum));
    for (i=1;i<=N;i++)
    for (j=1;j<=i;j++)
    scanf("%d",&a[i][j]);
    printf("%d",F(1,1));

}

动态规划定义:

将一个问题分解为子问题递归求解,并且将中间结果保存以避免重复计算的办法,成为动态规划递归的思想在编程时为必要实现为递归函数。
(一点个人不成熟的理解,应该会很适合新人的理解):递归的过程像是一个从上到下的过程,而递推更像是从下往上走,并且保证每一步都是最优解

动态规划解题的一般思路

大多数求最优解问题可以用动态规划的思想进行求解,把一个问题划分为若干个子问题,然后不只是进行单纯的递归在递归的过程中记录下子问题的解,这样就可以避免重复计算的问题,提高算法的速度。

在动态规划解题时,往往将和子问题相关的各个变量的一组取值成为状态,而某个状态下的值,就是这个状态的

在动态规划问题解题中,重要的是列写状态转移方程的思路,把握好边界,然后再向上不断地递推。

注:

只有满足以下俩个条件的问题才可以用动态规划的办法求解:

1.问题具有最优子结构性质。

即问题的最优解所包含的子问题的解也是最优的。

2,无后效性

就是说前面的状态值只要已确定,则在以后的演变过程中,则此后的演变只和这若干个状态的值有关,与之前是采取哪种手段或经过哪些路径演变到当前的若干状态无关(我理解为你前面每一步都是步都是最优解,自然在后面求最优解的时候不必为前面的答案发愁

在学习完动态规划后,我们给出用动态规划求解数字三角形问题的解:

//7__POJ1163数字三角形_动态规划
#include<stdio.h>

int a[102][102];
int N;
int maxsum[110][100];

main()
{

    int i,j;
    scanf("%d",&N);

    for (i=1;i<=N;i++)
    for (j=1;j<=i;j++)
    scanf("%d",&a[i][j]);
    for (j = 1;j<=N;j++)
        maxsum[N][j] = a[N][j];//边界条件,从第N行j列的数字开始走的解就是j的值
    for(i = N;i>1;i--)
    for (j=1;j<i;j++)
    {
        if (maxsum[i][j] > maxsum[i][j+1])
            maxsum[i-1][j] = maxsum[i][j] + a[i-1][j];
        else
            maxsum[i-1][j] = maxsum[i][j+1] + a[i-1][j];
    }
    printf("%d",maxsum[1][1]);

}

拓展

其实程序仍可以继续改写,使得maxsum数组用一维数组即可表示,这里不再给出代码,愿读者自己动手尝试。

上一篇:我的算法笔记(5)分治1

猜你喜欢

转载自blog.csdn.net/qq_33950926/article/details/90112562