从 Fibonacci 数列看“动态规划”思想

本文内容

  • 概述
  • 从 Fibonacci 数列看“动态规划”思想
  • 动态规划基础 
  • 动态规划步骤
  • 动态规划意义
  • 动态规划应用
  • 备注

概述

在数据结构中,最经典的算法/问题是:Floyd 算法(最短路径)、哈夫曼编码和 Fibonacci(斐波那契数列),背包问题等等。但当时,这些经典仅仅是描述了一个问题的解决方法,没有对整个这类问题更深入的阐述。

而事实上,随着对问题理解深入,发现这些算法和问题都包含了“动态规划”的思想。在此思想基础上,对这些算法和问题可以进行重大改进——算法更简单、时间复杂度更小。

“动态规划(Dynamic Programming,DP)”对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算。“Programming”是指一种规划,在这里以及线性规划中,都是指使用一种表格化的解法,而不是指写计算机代码。

从 Fibonacci 数列看“动态规划”思想

下面用 C# 演示斐波那契数列的一般递归算法,以及利用“动态规划”思想的改进算法。

using System;
 
namespace Fibonacci
{
    class Program
    {
        const int N = 6;
        static int[] m = new int[N] { 1, 1, 0, 0, 0, 0 };
 
        static void Main(string[] args)
        {
            Console.WriteLine("Fibonacci(5) = " + Fibonacci(5));
 
            
            Console.WriteLine("Fibonacci(5) with Dynamic Programming = " + Fibonacci_DP(5, ref m));
 
            Console.ReadKey();
        }
        /// <summary>
        /// 时间复杂度 O(n!)
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        static int Fibonacci(int n)
        {
            if (n == 0 || n == 1)
                return 1;
            return Fibonacci(n - 1) + Fibonacci(n - 2);
        }
        /// <summary>
        /// 时间复杂度 O(n)
        /// </summary>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        static int Fibonacci_DP(int n, ref int[] m)
        {
            if (m[n] == 0)
                m[n] = Fibonacci_DP(n - 1, ref m) + Fibonacci_DP(n - 2, ref m);
            return m[n];
        }
    }
}

运行结果

其中,Fibonacci 方法是斐波那契数列一般的递归算法。而 Fibonacci_DP 方法是利用“动态规划”思想的算法。

从时间复杂度上看,一般的递归算法是 O(n!),呈指数级增长。而采用“动态规划”思想的算法只有 O(n)

n=5 时,Fibonacci(5) 的计算过程如下:

fib(5) 
fib(4) + fib(3) 
(fib(3) + fib(2)) + (fib(2) + fib(1)) 
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1)) 
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1)) 

可以看出,这个递归算法,对每个子问题都要重新计算。而实际上,若利用“动态规划”思想这是没必要的。对于已经计算完的子问题,下次再遇到直接使用。将已经计算的结果保存在数组 m 中,在后面直接使用,避免重复计算。

在实际开发中,了解一些算法方面的知识,往往很有必要。能够分析自己的算法,比如你的算法在何种输入规模,效率是较高的。

个人觉得,在开发 ERP 系统时,会涉及很多算法。最近写一个关于改变工厂生产过程中变更加工工艺(客户称为“工艺再造”)的递归算法,猛然发现,满复杂的。当时有点惊讶。之前,除了上学时《数据结构》里的经典算法和典型问题外,思考得并不多。

下面是大概说明一下“动态规划”思想。

动态规划基础

采用“动态规划”方法解决最优化问题中的两个要素:最优子结构和重叠子问题。

  1. 最优子结构。子问题的最优解是问题的一个最优解。当一个问题具有最优子结构时,提示我们动态规划可能会适用。
  2. 子问题重叠。用来解原问题的递归算法可反复地解同样的子问题,而不是总在产生新的子问题。

动态规划步骤

动态规划思想可分为如下 4 个步骤:

  1. 描述最优解的结构。
  2. 递归定义最优解的值。
  3. 按自底向上的方式计算最优解的值。
  4. 由计算出的结果构造一个最优解。

第 1~3 步构成问题的动态规划解的基础。第4步在只要求计算最优解的值时可以略去。如果的确要做第 4 步,则有时要在第 3 步的计算中记录一些附加信息,使构造一个最优解变得容易。

动态规划意义

“动态规划”是运筹学的一个分支,是求解决策过程(Decision Process)最优化的数学方法。20 世纪 50 年代初,美国数学家 R.E.Bellman 等人在研究多阶段决策过程(Multistep Decision Process)的优化问题时,提出了著名的最优化原理(Principle of Optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957 年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。

动态规划在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。

虽然,动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但一些与时间无关的静态规划,如线性/非线性规划,只要人为地引进时间因素,视为多阶段决策过程,也可以用动态规划方法求解。

动态规划方法是解最优化问题的一种途径和方法,而不是一个特定算法。针对一种最优化问题,由于各种问题的性质不同,往往确定最优解的条件也不相同。也就不存在一种万能的动态规划算法。因此,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。

动态规划应用

  • 最优二叉查找树——如将一篇文章从英文翻译为法语,我们需要一棵最优二叉查找树。
  • 最长公共子序列(LCS)——用来比较生物的 DNA 序列。
  • Viterbi 算法——用动态规划做语音识别。
  • 矩阵链乘法——多个矩阵相乘时,相乘的顺序对效率影响很大。

备注

  • R.Bellman 在 1955 年开始系统地研究动态规划。虽然在之前已经知道最优化技术含有动态规划的元素,Bellman 给这个领域提供了坚实的数学基础。
  • Hu 和 Shing 给出了矩阵链乘法的一个 O(nlgn) 时间的算法。
  • 最长公共子序列问题的 O(mn) 时间算法是一个一般的算法。 Knuth 提出了 LCS 问题的次二次方的算法是否存在的问题。Masek 和 Paterson 给出一个在 O(mn/lgn) 时间内执行的算法来肯定地回答这个问题,其中 n<=m 而且此序列是从一个有界集合中而来。在输入序列中没有元素出现超过一次的特殊情况中,Szymanski 说明这个问题可在 O((n+m)lg(n+m)) 时间内解决。这些结果中偶许多延伸到了计算字符串编辑距离的问题上。
  • 一篇由 Gilbert 和 moore 撰写的关于可变长二元编码的早期论文有这样的应用:在所有的概率 pi 都是 0 的情况下构造最优二叉查找树;这篇论文一个 O(n^3) 时间的算法。Aho、Hopcroft 和 Ullman 给出了该算法。Hu 和 Tucker 设计了一个算法,它在所有的概率 pi 都是 0 的情况下,使用 O(n^2) 的时间和 O(n) 的空间;随后,Knuth 把时间降到 O(nlgn)

下载 Demo

原文地址:https://www.cnblogs.com/liuning8023/p/2558315.html