经典中的经典算法 动态规划(详细解释,从入门到实践,逐步讲解)

动态规划的重要性就不多说,直接进入正题

首先,我们看一下官方定义:

定义:

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

基本思想与策略编辑:

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

(来自百度百科)

说实话,没有动态规划的基础很难看懂,但是也能从中看出一些信息,下面我翻译成人话:

首先是拆分问题,我的理解就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现.

关键就是这个步骤,动态规划有一类问题就是从后往前推到,有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前一步推导,得到前一步的最佳选择

然后就是定义问题状态和状态之间的关系,我的理解是前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)

我们再来看定义的下面的两段,我的理解是比如我们找到最优解,我们应该讲最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度

说很难理解清楚,容易懵懵懂懂的,所以下面结合实例看一下(建议结合实例,纸上谈兵不太好):

经典的数字三角形问题(简单易懂,经典动态规划);

可以看出每走第n行第m列时有两种后续:向下或者向右下

由于最后一行可以确定,当做边界条件,所以我们自然而然想到递归求解

解题思路:

下面简单写一下java代码:

//java代码纯属自己练习,标准答案参考上面的c语言答案
class solution{
    public int getMax(){
        int MAX = 101;
        int[][] D = new int[MAX][MAX];   //存储数字三角形
        int n;              //n表示层数
        int i = 0; int j = 0;
        int maxSum = getMaxSum(D,n,i,j);
        return maxSum;
    }
    public int getMaxSum(int[][] D,int n,int i,int j){
        if(i == n){
            return D[i][j];
        }
        int x = getMaxSum(D,n,i+1,j);
        int y = getMaxSum(D,n,i+1,j+1);
        return Math.max(x,y)+D[i][j];
    }
}

其实仔细观察,上面的解答过程时间复杂度难以想象的大,那是因为他对有的数字的解进行了多次的重复计算,具体如下图:

如果不明白上图,可以把每条路径都画出来,观察每个数字有多少条路径经过了他,就会一目了然

然后我们就可以自然而然的想到,如果我们每次都把结果保存下来,复杂度就会大大降低

其实答案很简单:

其实,仔细观察该解题过程,该过程就是标准的动态规划解题过程,如果把该过程画出来(找到每一步的最优解,其他的舍弃)对动态规划会有更深刻的解法

还有就是,递推的另一个好处是可以进行空间优化,如图:

下面总结一下动态规划的解题一般思路:

首先递归应该是我们解决动态规划问题最常用的方法,帅,速度不算太慢

那么递归到动规的一般转化方法为:

如果该递归函数有n个参数,那么就定义一个n维数组,数组下标是递归函数参数的取值范围(也就是数组每一维的大小).数组元素的值就是递归函数的返回值(初始化为一个标志值,表明还未被填充),这样就可以从边界值开始逐步的填充数组,相当于计算递归函数的逆过程(这和前面所说的推导过程应该是相同的).

动规解题的一般思路(标准官方,不过经过前边讲解应该就能理解了):

将原问题分解为子问题(开头已经介绍了怎么分解)

(注意:1,子问题与原问题形式相同或类似,只是问题规模变小了,从而变简单了;

2,子问题一旦求出就要保存下来,保证每个子问题只求解一遍)

确定状态(状态:在动规解题中,我们将和子问题相关的各个变量的一组取值,称之为一个"状态",一个状态对应一个或多个子问题所谓的在某个状态的值,这个就是状态所对应的子问题的解,所有状态的集合称为"状态空间".我的理解就是状态就是某个问题某组变量,状态空间就是该问题的所有组变量) 另外:整个问题的时间复杂度就是状态数目乘以每个状态所需要的时间

确定一些初始状态(边界条件)的值 (这个视情况而定,千万别以为就是最简单的那个子问题解,上面只是例子,真正实践动规千变万化)

确定状态转移方程 (这一步和第三步是最关键的 记住"人人为我"递推,由已知推未知)

适合使用动规求解的问题:

1,问题具有最优子结构

2,无后效性 说的花里胡哨的,其实一般遇到求最优解问题一般适合使用动态规划

原文地址:https://www.cnblogs.com/linhaostudy/p/12243011.html

时间: 2024-08-07 20:29:01

经典中的经典算法 动态规划(详细解释,从入门到实践,逐步讲解)的相关文章

SSM:spring+springmvc+mybatis框架中的XML配置文件功能详细解释

SSM:spring+springmvc+mybatis框架中的XML配置文件功能详细解释 2016-04-14 23:40 13030人阅读 评论(2) 收藏 举报 分类: SSM(7) 这几天一直在整合SSM框架,虽然网上有很多已经整合好的,但是对于里面的配置文件并没有进行过多的说明,很多人知其然不知其所以然,经过几天的搜索和整理,今天总算对其中的XML配置文件有了一定的了解,所以拿出来一起分享一下,希望有不足的地方大家批评指正~~~ 首先   这篇文章暂时只对框架中所要用到的配置文件进行解

Android开发中的设计模式—单例模式的详细解释

Android开发中的设计模式-单例模式的详细解释: 1. 单例模式的特点: (1).保证一个类只有一个实例 (2).提供一个能访问到他的全局访问点. (3).构造函数声明为私有的,从而阻止了在类外创建对象 2. 种类: (1).饿汉式单例模式 (2).懒汉式单例模式 3. 代码分析: (1).饿汉式单例模式: //类加载的时候对象就实例化了. private static Single mSingle = new Single(); public static Single getInstan

Android中ViewHolder模式开发的详细解释

Android开发中ViewHolder模式开发的详细解释: 1.ViewHolder的解释: (1).只是一个静态类,不是Android的API方法. (2).它的作用就在于减少不必要的调用findViewById,然后把对底下的控件引用存在ViewHolder里面,再在View.setTag(holder)把它放在view里,下次就可以直接取了. 2.convertView中的TAG: (1).Tag不像ID是用标示view的.Tag从本质上来讲是就是相关联的view的额外的信息.它们经常用

递归输出字符串 经典中的经典

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> void f(char *p) { if (*p == '\0') { return; } else { f(p + 1); printf("%c\n", *p); } } void main() { char *p = "abcd"; f(p); system("pause"); }

源码方式向openssl中添加新算法完整详细步骤(示例:摘要算法SM3)【非engine方式】

openssl简介 openssl是一个功能丰富且自包含的开源安全工具箱.它提供的主要功能有:SSL协议实现(包括SSLv2.SSLv3和TLSv1).大量软算法(对称/非对称/摘要).大数运算.非对称算法密钥生成.ASN.1编解码库.证书请求(PKCS10)编解码.数字证书编解码.CRL编解码.OCSP协议.数字证书验证.PKCS7标准实现和PKCS12个人数字证书格式实现等功能. openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能.openssl支持Linux.UNIX.wi

Java堆、栈和常量池以及相关String的详细讲解(经典中的经典)

博客分类: Java综合 一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配.你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象. ------最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 堆栈(stack).位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持.堆栈指针若向下移动,则分配新的

白帽Hacker之路:(二)经典中的经典之ipc$入侵——建立IPC$漏洞

(一)什么是IPC$入侵? IPC$入侵,即通过使用Windows 系统中默认启动的IPC$共享,来达到侵略主机,获得计算机控制权的入侵.此类入侵主要利用的是计算机使用者对计算机安全的知识缺乏,通常不会给计算机设置密码或者密码过于简单,因此才导致被黑客的有机可乘. (二)如何查看IPC$共享? 第一步:右键 计算机->管理 第二步:共享文件夹->共享->右侧 即可查看共享的文件夹 (三)如何创建IPC$漏洞? 第一步:win+r   输入gpedit.msc,点击确定,打开本地组策略编辑

经典中的经典Unique Binary Search Trees II

Unique Binary Search Trees II 原题: Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. For example, Given n = 3, your program should return all 5 unique BST's shown below. 1 3 3 2 1 \ / / / \ 3 2 1 1 3 2 / /

POJ 1185 炮兵阵地 (状压dp 经典中的经典)

炮兵阵地 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 21381   Accepted: 8290 Description 司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队.一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图.在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队):一支炮兵部队在地图上的攻击