算法导论15章 动态规划 dynamic programming 复习

本文深入探讨动态规划的概念,将其与数学归纳法对比,指出动态规划常用于最优问题。通过分析,强调动态规划的核心在于递推式,并讨论了其与递归的区别。文中以矩阵链式乘法为例,展示了如何通过决策树寻找递推式,并讨论了动态规划在处理离散数据上的局限性。
摘要由CSDN通过智能技术生成

尽管已经是第5次看算法导论这本书了,每次看都希望会有更深地理解。
现在就开始看书吧。
动态规划在我的理解中就像是数学归纳法一样的存在。
它俩都是解决问题的方法。而且比较类似。
书中说:动态规划常常被应用于最优问题求解。
提问和理解:
问题1:动态规划真的只能被用于最优问题吗?举个栗子。
回答:在充分理解动态规划的核心思想之前很难判断这个答案。
那么动态规划的核心思想是什么呢?动态规划解决问题的潜能有多大呢?
是把问题分解成已经解决的更小的子问题吗? 感觉这说的是递归。不过动态规划和递归思想有深刻的联系。因为递归是将问题变成更小的未解决的子问题,而DP是将问题变成更小的已经解决的子问题。好像反过来一样。
但还有一个重大区别,就是DP能解决的问题的范围远远小于递归方法。这体现在动态规划方法绝对是存在一个递推式来进行递推的。举个栗子就是二叉树的先序遍历,使用递归方法就能解决遍历问题。而动态规划无法去解决,因为二叉树的遍历根本就不存在递推式,最新的遍历结果和之前的节点访问没有联系。这造成了DP解决问题的能力小于递归方法。那么在反过来提问:
**问题2:**DP能解决的问题递归方法一定能解决吗?
回答:感觉确实是如此,因为DP的关键在于一个递推式,假设递推式是一个n元的”函数“
f(x1,x2,x3,,,xn);其中x1,x2,,xn都是整数。那么递推地过程就是缩小x1,x2,,,xn的大小,最终达到一个初始的已经解决了的问题。那么在减小规模的过程中一定会再次出现规模更小的f“函数”。这样就能利用f函数作为递归函数,所以DP能解决的问题,递归也全都能解决。
DP的关键点在于递推式。刚才发现一个问题,就是DP的递推式的“自变量”x1,x2,,,xn必须是整数,浮点数是无法递推地。因为浮点数是无限的,不可能记录无限个数据。也就是DP还有一个限制,那就是递推地数据必须是离散的,而不是连续的。这一点就让DP失去了巨大的潜能。因为递归方法是可以处理浮点数的,比如求100位精度的某个小数,递归可以在最后一位小数点达到一百位的时候停止。而动态规划不可能去记录100位小数的所有可能性。毕竟10^100次方个数。
书上关于使用DP的几个步骤:
1.将最优问题描述成符号.
2.推导出递推式。
3.通过递推式计算,通常是自底向上的计算。
4.从计算结果获得答案。
我能说这4点对于我而言第1点最有启发意义吗?,确实为了解决最优问题,如何用符合来描述问题是很重要的。比如0-1背包问题,如何在总重有限的背包的问题带更多的东西走。这里那些数据需要字符化是只能从经验上知道,之前已经分析了,DP解决离散化问题,这里的离散数据包括哪些呢? 其一,有限个东西,每个东西要么带走要么留下,离散数据。但是只有这就够了吗?如果人能携带的总重量是个浮点数,并且每个物品重量也是位数不定的浮点数呢?这样也无法使用动态规划。在这个问题里面就体现了不知道该为那些离散量建立符号表示成为了主要困难,选择一个好的离散量来得出递推式是很重要的。而在0,1背包问题中怎么知道重量的离散化对于递推式是必不可缺德量呢?或许我在0-1背包问题中知道,因为我做过。但是换一个问题要怎么才能知道呢?而且离散变量必须是均匀的不能是0.1,0.01,0.001,0.0001….这样子不均匀的离散值。他们之间的间隔必须是某个值的整数倍。
问题3:如何知道递推式中需要哪些离散变量?
回答:这必须从决策树中开始。比如0-1背包问题
每一个东西要么带走要么留下,就有2^N种可能性。那么就产生一个有2^N个叶节点的决策树,
在这个决策树的节点上附加一些信息,很容易就知道并不需要这么大的决策树,因为某些东西一旦带走,它后面根本就无法在带走别的东西了,所以2^N个叶节点的决策树过于臃肿,通过附加重量限制就可以缩小决策树规模,所以重量在这里是需要符号化的,至于是否在递推式中需要重量这还无法讨论。另外一个需要符号化的离散数据就是价值,在决策树中如果不记录每个叶节点的价值就没法决定哪个选择才是最好的。
书上的rod-cutting问题和0-1背包问题其实是一个问题。
在决策树中寻找重复信息,把重复信息统一起来获得递推式。
所以决策树是获得递推树的重要方法。
再看看书中另外一个例子matrix-chain multiplication 矩阵链式乘法。
如何决定乘法顺序使得整个矩阵A1*A2*A3*A4的使用的乘法过程最小,并输出这个乘法过程的使用数量。
与书上的4步分析方法不同,我使用决策树来分析问题。
首先对于n个矩阵A1,A2,,,,,,An。它们有行数Ai.low,列数Ai.column。其中
Ai.column = Ai+1.row。那么使用原始乘法依次顺序乘起所有的矩阵A1*A2*****An需要的乘法总数为,
A1.row*A1.column * A2.column +
A1.row * A2.column * A3.column +
A1.row * A3.column * A4.column +
.。。。。。
A1.row * An-1.column * An.column
= A1.row * (A1.column * A2.column + A2.column * A3.column +….+An-1.column * An.column )。
最终矩阵A = A1*A2*****An 有A.row = A1.row,A.column = An.column。
而这里所需要做的是通过安排不同顺序的乘法先后顺序,获得最小的乘法总数。
由于n+1个矩阵有n次矩阵乘法。所以决策树最多有n!个叶结点。在不同的情况下每一种序列都是有可能的,不过这里存在重复情况只能说绝对的上限。更合适的方法还是书上的推理方法:
这里写图片描述
最终获得卡特兰数。
首先可以肯定的几个点是,
递推式必然会缩小规模。
从决策树上看,每次在n个乘法中选择出第一个乘法作为第一次运算的乘法需要对每一个乘法都尝试一遍。在这样一个过程中,n个乘法的决策树被切割成了n个最大只可能是n-1个乘法的子决策树。这样就缩小了规模。为了重复利用数据,需要记录所有[i,j]区间内的乘法的最小乘法总数即可。并从j-i = 0开始算起,到j-i = 1,j-i = 2..的所有[i,j]。
假设另dp(i,j)为i号乘法开始到j号乘法结束的乘法的最小乘法总数。
mul(i,j)为i号乘法开始到j号乘法结束的乘法的结果矩阵的宽和高。
dp(0,n-1)一共n+1个矩阵相乘。
dp(0,n-1)=min(dp(0,i-1) +dp(i+1,n-1) + mul(0,i).row*mul(0,i).column*mul(i+1,n-1).column)
明显看出来的是每次过程都缩小了计算规模,从[0,n-1]一共n个元素,而[0,i-1]一共i个元素,[i+1,n-1]一共n-i-1个元素.而且不论i = 0,还是i = n,每次规模都至少会缩小1.所以我只需要从距离为dp(i,j) j-i = 0开始算,迭代到dp(h,m) m - h = 1.然后再到m-h = 2。最后到m-h = n-1就完成了dp(0,n-1)的计算。这个过程的时间O(n^2)的时间计算出最少乘法计算量的数值。
令p(i)为计算前i个乘法能取得的最小的乘法总数。q(i)为计算后i个乘法能取得的最小的乘法总数。

#include <stdio.h>
#include <memory.h>
#include <malloc.h>
#include <limits.h>
void matricx_chain_order(int p[],int ***a,int ***b,int size)
{
    int n = size - 1;
    *a = (int **)malloc(sizeof(int *)*size);
    *b = (int **)malloc(sizeof(int *)*size);
    int **m = *a;
    int **s = *b;
    for(int i = 0;i<size;i++){
        m[i] = (int*)malloc(sizeof(int)*size);
    //  memset(m[i],0,sizeof(int)*size);
        s[i] = (int*)malloc(sizeof(int)*size);
    //  memset(s[i],0,sizeof(int)*size);
    }
    for(int i = 0;i<size;i++)
        m[i][i] = 0;
    for(int l = 2;l<size;l++)
    {
        for(int i = 1;i<=n-l+1;i++)
        {
            int j = i + l - 1;
            m[i][j] = INT_MAX;
            for(int k = i;k<j;k++)
            {
                int q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                if(q < m[i][j])
                {
                    m[i][j] = q;
                    s[i][j] = k;
                }
            }
        }
    }
    printf("min1 = %d\n",m[1][9]);

}
int main()
{
    int **a,**b;
    int p[10] = {1,1,1,1,1,1,1,1,1,1};
    matricx_chain_order(p,&a,&b,10);
    printf("min = %d\n",a[1][9]);
    return 0;
}

最后还是直接使用书上的方法,区别只在于书上以矩阵数量[1,n]为递推对象,而我以乘法数量[0,n-1]为递推对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值