动态规划问题

动态规划问题

动态规划是一种求最优解的算法思想,它同分治法类似,都是讲问题划分为若干个独立的子问题,解决完子问题之后就能得到该问题的解。

斐波拉契数列

斐波拉契是一个常见的数列,其中的每一个数都是又它的前两个数相加的出来的,其公式如下:
f ( x ) = { 1 , x = 0 & x = 1 f ( x − 1 ) + f ( x − 2 ) , x > = 2 f(x) = \begin{cases} 1, & x=0\&x=1 \\ f(x-1)+f(x-2), & x>=2 \end{cases} f(x)={1,f(x1)+f(x2),x=0&x=1x>=2
我们很容易就能写出对应的递归函数:

def fibonacci(x):
    if x == 1 or x == 2:
        return 1
    return fibonacci(x - 1) + fibonacci(x - 2)

这是一个典型的分治法的递归式。但是,这存在一个问题,那就是当数字足够大时,会进行大量的重复计算:

在这里插入图片描述

分治法是不会记录子问题的结果的,它只会频繁地计算子问题。而动态规划会考虑公共子问题,也就是那些被重复计算的部分。动态规划会将这些问题记录下来,避免每次遇到这些重复的子问题时重新反复的计算。
在这里,我们定义一个数组,用于存储其计算的结果:

def fibonacci2(x):
    temp = [0 for i in range(x)]
    temp[0], temp[1] = 1, 1

    def run(i):
        if temp[i] == 0:
            temp[i] = run(i - 1) + run(i - 2)
        return temp[i]

    return run(x-1)

矩阵链乘法问题

由于 n n n个矩阵进行相乘 A 1 ∗ A 2 ∗ . . . ∗ A n A_1*A_2*...*A_n A1A2...An。我们知道,再两个矩阵相乘 A n ∗ m ∗ B m ∗ k A_{n*m}*B_{m*k} AnmBmk时,根据矩阵乘法的运算规则,其会进行 n ∗ m ∗ k n*m*k nmk次数乘运算:

2 ∗ 3 2*3 23 3 ∗ 2 3*2 32的矩阵相乘运算了 2 ∗ 3 ∗ 2 2*3*2 232数乘运算:
( a b c d e f ) ∗ ( a b c d e f ) = ( a ∗ a + b ∗ c + c ∗ e a ∗ b + b ∗ d + c ∗ f d ∗ a + e ∗ c + f ∗ e d ∗ b + e ∗ d + f ∗ f ) \begin{pmatrix} a & b & c \\ d & e & f \\ \end{pmatrix} * \begin{pmatrix} a & b \\ c & d \\ e & f \\ \end{pmatrix} = \begin{pmatrix} a*a+b*c+c*e & a*b+b*d+c*f \\ d*a+e*c+f*e & d*b+e*d+f*f \\ \end{pmatrix} (adbecf) acebdf =(aa+bc+ceda+ec+feab+bd+cfdb+ed+ff)

当三个矩阵( n ∗ m , m ∗ k , k ∗ s n*m, m*k, k*s nm,mk,ks)相乘的时候,在先进行前两个矩阵运算时得到结果的运算次数为 n ∗ m ∗ k + n ∗ k ∗ s n*m*k+n*k*s nmk+nks, 而先进行后两个举证运算时得到结果的运算次数为 m ∗ k ∗ s + n ∗ m ∗ s m*k*s+n*m*s mks+nms。可以看出,矩阵乘法运用结合律后虽然得到的结果相同,但是我们的计算成本不同。

这样,我们可以运用结合律来减少运算次数来得出 n n n个矩阵相称的结果。那么,这个最少的运算次数是多少?

在这里,我们将第 n n n个到第 m m m个矩阵的运算结果标记为 S n m S_{nm} Snm,第 i i i个矩阵的行数为 p i p_i pi,列为 p i − 1 p_{i-1} pi1,我们 S n m S_{nm} Snm A k A_{k} Ak处进行拆分,得到 S n m = S n k ∗ S k m S_{nm}=S_{nk}*S_{km} Snm=SnkSkm,这样,我们计算 S n m S_{nm} Snm的成本就是计算 S n k S_{nk} Snk S k m S_{km} Skm与这两个结果矩阵相乘的和,我们将 m m m n n n的计算成本记为 m n m m_{nm} mnm:
m n m = { 0 , m = n min ⁡ n < = k < n ( m n k + m k m + p m − 1 ∗ p k ∗ p n ) , m < n m_{nm} = \begin{cases} 0, & m=n\\ \min_{n<=k<n}({m_{nk}+m_{km}+p_{m-1}*p_{k}*p_{n}}), & m<n \end{cases} mnm={0,minn<=k<n(mnk+mkm+pm1pkpn),m=nm<n
可以得出,我们在冲第 n n n个到第 m m m个矩阵组成的矩阵链中,用任意的第 k k k个矩阵将其拆分成两个不同的字矩阵链并得到其结果,再对这些结果进行比较,便可以得到计算开销最小的那一个。根据该公式,我们也能很容易写出其对应的递归代码。

动态规划基础

最优子结构

利用动态规划来解决问题,首先第一步就是描述其最优子结构。在斐波拉契中,我们求第 n n n个数可以通过第 n − 1 n-1 n1和第 n − 2 n-2 n2个数来求解,而在矩阵链相乘的问题中,也可以将这个矩阵链拆分成两个子矩阵链。这些问题中,我们通过子问题的最优解来构造其最优解。动态规划问题是一种自底向上的方法,也就是说在解决一个问题时,首先要解决它的子问题的最优解。我们可以发现,这些问题的解都可以看作是做一个选择,比如矩阵链乘法问题就是选择合适的矩阵对其进行拆分,并且在这些问题中,其最优解的子问题也一堆是最优解。
在下图中,我们可以求得A-C-DAD的最短路径,而且它的两个子路径A-CC-D也是最短路径,也就是说,这是他的最优子结构。

在这里插入图片描述

但是,若是换成求最长简单路径就不一样了。我们同样可以看出A-C-DAD的最长简单路径,但是A-CC-D却不是其最长简单路径,其最长简单路径为A-B-D-CC-A-B-D
为什么这两个问题会有如此不同呢?这是因为最短路径问题的子问题是独立的,而最长简单路径问题的子问题不是独立的。

子问题独立是指一个子问题的解不会影响到另外的子问题的解。在最短路径中,A-CC-D这两个最有子问题中间没有重复访问的节点,彼此是相互独立的。而在最长简单路径问题中,A-B-D-CC-A-B-D这两条路径又重复访问的节点,这样合并这两条路径时得到的就不是一条简单路径,所以这两个子问题不是独立的。

重叠子问题

当一个递归算法在不断的调用同一个问题时,我们就说它具有重叠子问题。这也是动态规划和分治法不同的地方。前面提到分治法只是会将问题分解成子问题再计算,其每一步都产生全新的子问题,而动态规划则是利用这些重叠的子问题,这些重叠子问题只会计算一遍,然后存储起来,等到又一次用到的时候就直接的出结果,比如在斐波拉契问题中,我们就利用列表存储了计算出来的结果,在下次使用的时候直接查表而不消耗额外的时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值