Fibonacci数列 递归 杨辉三角 动态规划

这篇帖子主要是介绍 递归 与 动态规划 之间是如何转换的,
什么是递归估计不用多说,那么什么是 动态规划呢?
我理解的动态规划就是:
能够避免 直接 递归实现 中出现的重复运算的技术就是动态规划。

这里有三个关键词:"直接"、"递归实现"、"重复运算"

也许最简单的, 也是众所周知的动态规划题目是 Fibonacci数列
如果你不知道,说明你的老师也不知道,而且我为你感到难过。

但是,遗憾的是,本文将 选取另一个也是众所周知的动态规划题目: 
二项式系数 C(m, n)

它的递推公式如下:

C(m, n) = C(m-1, n-1) + C(m-1, n), C(m, 0) = 1, C(m, m) = 1;

你或许会很惊讶,这是什么?我根本不熟悉嘛!
好吧,我想杨辉三角你应该不陌生,是个c语言的书籍都会有这道题,
那么,你还记不记得杨辉三角 与 二项式系数有那么一点点的关系呢?
记不得,翻书吧!

首先,我(不是我们)给出 C(m, n)的 直接递归实现

#include <stdio.h>

int C(int M, int N);

int main(void)
{
    printf("%d\n", C(5, 2));

    getchar();

    return 0;
}

int C(int M, int N)
{
    
    if (N == 0 || M == N)
    {
        return 1;
    }

    return C(M-1, N-1) + C(M-1, N);
}

输出:10

那么, 这个实现直接吗?
当然直接,公式就是这样定义的啊。不是吗?

然后, 可以肯定的是这个实现有 重复运算,
肯定有,要不然不会拿这道题作为例程解析。
怎么样知道这个实现有 重复运算呢?

很简单,把递归调用路径打印出来看一眼就行了,仅仅是一眼。
在此之前,得确信你会 复制/粘帖 控制台中的内容,如果你还不会,
那么,请你百度一下吧。

我给函数多加一个参数用来记录 递归调用深度 (这项技术已经被我多次使用,不知你的技术敏感度怎么样,有没有留意过)?
然后,重新实现一下代码。

#include <stdio.h>

int C(int M, int N, int depth);

int main(void)
{
    printf("\nc(5, 2) = %d", C(5, 2, 0));

    getchar();

    return 0;
}
int C(int M, int N, int depth)
{
    int i, ret;

    for (i = 0; i < depth; i++)
    {
        printf(" ");
    }

    printf("C(%d, %d)\n", M, N);

    if (N == 0 || M == N)
    {
        return 1;
    }

    return C(M-1, N-1, depth+1) + C(M-1, N, depth+1);


输出:

C(5, 2)
C(4, 1)
  C(3, 0)
  C(3, 1)
   C(2, 0)
   C(2, 1)
    C(1, 0)
    C(1, 1)
C(4, 2)
  C(3, 1)
   C(2, 0)
   C(2, 1)
    C(1, 0)
    C(1, 1)
  C(3, 2)
   C(2, 1)
    C(1, 0)
    C(1, 1)
   C(2, 2)

c(5, 2) = 10

很明显的可以看出,C(1, 1)被多次运算过,当然不只是C(1, 1)了, 还有C(2, 1)呢?还有,...?

然后,怎么样避免这个实现中的重复运算呢?
还记得先前有个帖子 "打家看看这个递归的 为什么结果要存起来 不能直接返回T 求N的阶乘的递归都不要存结果" 吗?

好吧,接下来咱采用相同的技术:把结果保存起来

#include <stdio.h>

#define M 5
#define N 2

int C(int m, int n, int depth);

int knownC[M+1][N+1] = {0};

int main(void)
{
    printf("\nc(%d, %d) = %d\n", M, N, C(M, N, 0));
    getchar();
    return 0;
}

int C(int m, int n, int depth)
{
    int i, ret;

    for (i = 0; i < depth; i++)
    {
        printf(" ");
    }

    printf("C(%d, %d)\n", m, n);

    if (knownC[m][n] != 0)
    {
        return knownC[m][n];
    }

    if (n == 0 || m == n)
    {
        ret = 1;
    }
    else
    {
        ret = C(m-1, n-1, depth+1) + C(m-1, n, depth+1);
    }

    return knownC[m][n] = ret;
}

输出:
C(5, 2)
C(4, 1)
  C(3, 0)
  C(3, 1)
   C(2, 0)
   C(2, 1)
    C(1, 0)
    C(1, 1)
C(4, 2)
  C(3, 1)
  C(3, 2)
   C(2, 1)
   C(2, 2)

c(5, 2) = 10

这个就是 所谓的 "自顶向下动态规划"

现在, 我们回到先前 所说的 "杨辉三角"
利用杨辉三角解 C(m, n) 。

#include <stdio.h>

#define M 5
#define N 2

int main(void)
{
    
    int C[M+2][M+2] = {0, 1};
    int i, j;

    for (i = 1; i < M+2; i++)
    {
        for (j = 1; j <= i; j++)
        {
            C[i][j] = C[i-1][j-1] + C[i-1][j];
        }
    }

    printf("%d\n", C[M+1][N+1]);

    return 0;
}

这个就是 所谓的 "自底向上动态规划"


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值