`
tolys
  • 浏览: 116100 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

动态规划,递归与非递归,FP 之野望,描述与计算

阅读更多
1. 动态规划 动态规划这四个大字在算法设计这一块算是鼎鼎有名。它不是什么高深的东西,就是算法设计里面最基础的法门,少林长拳是也。基本上判断一个人是不是真的学过算法,考考动态规划就知道了。有趣的是,这动态规划偏偏就是和递归较劲的,而这个最简单的例子就是著名的肥婆拉车 …… 呃,斐波纳契(Fibonacci)数列了。 这个数列是这么定义的(知道的同学请跳过这一段):记 fib(n) 为斐波纳契数列的第 n 个元素,则 fib(0) = 0, fib(1) = 1, 对于所有的 n > 1 ,有 fib(n) = fib(n-1) + fib(n-2) 。巧得很,那位 ID 很长的 M 朋友(以后直接用 M 简称了,相信 007 不会介意),要计算的函数差不多也是这么个形式,所以下面的讨论,基本上可以一字不差的套用过去。 要计算这个函数,最直截了当的实现就是用递归了:
Java代码 复制代码
  1. int fib(int n)   
  2. {   
  3.     // 不要和我扯什么函数参数检查之类,喜欢抠这些的请去隔壁的对日外包培训班   
  4.   
  5.     if (n == 0)        return 0;   
  6.     else if (n == 1)   return 1;   
  7.     else               return fib(n-1) + fib(n-2);   
  8. }  
这个实现基本上就是照抄数列的定义,任何智商正常的程序员都能写出来、看得懂。但是如果说起运行效率,那完全可以用一坨有机肥料来形容。嗯嗯,当然了,搞算法的人不可以信口开河,光说人家像有机肥料是不行的,你得严格证明这一点,那么现在就让我们来简单分析一下:记 fib(n) 运行所需的时间为 T(n),显然 T(0) 和 T(1) 是常数,而对于所有的 n > 1, T(n) = T(n-1) + T(n-2) + 某个常数。Javaeye 不支持 latex,数学公式展不开,我们就取个最粗略的分析。显然,对于所有的 n > 1,T(n) 是单调增加的,满足 T(n) > T(n-1) > T(n-2) ... (有兴趣的同学可以数学归纳一把确证一下),因此 T(n) = T(n-1) + T(n-2) + 某个常数 > 2*T(n-2) ,所以 T(n) = Ω(2^{n/2})。通俗点说,n 每增大 2,T(n) 至少要翻一倍,如果 n = 256 ,那么这段程序多半是没有希望在宇宙毁灭之前运行完了。 这见鬼的肥婆 …… 斐波纳契数列真的那么难算么?当然不是,上面那段程序的问题在于包含了太多的重复计算。让我们看下面这张图(此图无耻地盗链自 SICP 的公开电子书): 看图,这是上面那个递归程序计算 fib(5) 的过程。一眼可以看出,这里面冗余无数,比如fib(5) 那棵计算 fib(3) 的右子树和 fib(4) 的左子树完全是相同的。这种冗余计算耗费了几乎全部的程序运行时间,从而使得这个程序的运行效率散发出有机肥料的气味。只要消除这些冗余,就能让程序运行速度快起来。 那么具体如何着手呢?看看那个递归式子,fib(n) = fib(n-1) + fib(n-2) ,从本质上来讲,这个式子真正告诉我们的是这样一件事:计算 fib(n) 这个任务,可以分解为计算 fib(n-1) 和计算 fib(n-2) 这两个新任务,而计算 fib(n-1) 和 fib(n-2) 这两个新任务,也可以继续通过这个递归式子分解下去,直到我们遇到 fib(0) 和 fib(1) 。算一算,我们一共可能遇到几个不同任务?fib(0) 到 fib(n) 一共 n + 1 个,而且我们有了 fib(0) 和 fib(1) ,就能算出 fib(2),有了 fib(1) 和 fib(2) 就能算出 fib(3) …… 以此类推,我们可以照着这个顺序把这区区 n + 1 个计算任务全都给完成了,从而也就得到了 fib(n)。程序如下: 这个程序的时间复杂度是 O(n) ,也就是线性时间(看不出来的回去重修数据结构),传个 n = 256 进去,瞬间你就能看到结果 —— 我们就这样把一个本来需要计算到宇宙末日的问题给解决了。 这种算法设计技巧,就是动态规划 我相信有些同学一定觉得上面这个例子实在太简单了,你们天才的大脑需要更有挑战性的工作才能满足,所以下面给个稍微复杂点的问题作为思考题,有兴趣的同学可以想一想。这是我面试某公司时遇到的面试题:话说有个魔法字典,其中记录了一些魔力单词(字符串),如果一个句子(也是字符串)可以被完全分解为若干魔力单词的拼接,那么这个句子就是一条咒语。假设我们可以用常数时间查询魔力字典是否包含一个特定的单词,那么现在给你一个句子,让你写一个程序判断这个句子是否一条咒语。用动态规划可以解哦。
评论

相关推荐

    c++合并排序算法递归与非递归方式

    c++实现的合并排序算法 用递归和非递归两种方式实现的

    c语言 二叉树应用:创建、递归非递归遍历、计算结点、分支、交换子树

    非递归先序遍历二叉树: 非递归中序遍历二叉树: 非递归后序遍历二叉树: 非递归中序遍历二叉树(算法2): 层次遍历二叉树: 递归计算单分支结点: 递归计算双分支结点: 递归计算叶子数: 二叉数的深度:...

    背包问题 动态规划递归算法)

    总之,掌握背包问题的动态规划递归算法对于解决实际中的资源分配、任务调度等问题具有重要意义,同时也对理解和运用动态规划与递归算法有着基础性的帮助。通过深入理解并熟练运用这一算法,我们可以在面对复杂优化...

    Ackermann 递归与非递归两种解法

    Ackermann 函数是一种著名的计算问题,它在计算机科学中被用作展示递归函数的例子,尤其是在讨论递归的深度和复杂性理论时。这个函数是由数学家Maurice Ackermann在1937年提出的,它不是组合函数,也就是说,不能...

    二叉树的创建与三种遍历的递归与非递归实现

    二叉树的创建与三种遍历的递归与非递归实现 包括二叉树的动态创建,前序遍历,中序遍历,后续遍历的递归与非递归方法的实现。

    C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法

    本文实例讲述了C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法。分享给大家供大家参考,具体如下: /*求二叉树叶子节点个数 -- 采用递归和非递归方法 经调试可运行源码及分析如下: ***/ #include #...

    快速选择非递归与递归算法实现

    在本主题中,我们将深入探讨快速选择的非递归和递归实现。 ### 快速选择的基本思想 快速选择的核心在于“分区”操作。首先,我们随机选择一个基准元素,然后将数组分为两部分:小于基准的元素和大于或等于基准的...

    java编写的递归与非递归

    ### Java中的递归与非递归编程技巧 在Java编程中,递归和非递归是两种常用的处理问题的方法。递归通常用于解决那些可以通过分解为更小问题来解决的问题,而非递归则更适合于使用迭代的方式来解决问题。下面将详细...

    阿克曼函数 c程序 递归与非递归算法的综合

    阿克曼函数是一种非常特殊的数学函数,它在计算理论和计算机科学中被广泛用来探讨递归的概念。这个函数因其复杂的性质而闻名,特别是在其参数达到一定值时,增长速度极其迅速,以至于很快超出任何可计算的范围。在这...

    ackermann函数的递归实现和非递归实现

    总结,阿克曼函数的递归和非递归实现都是理解递归、堆栈以及数据结构在计算复杂性中的作用的重要案例。非递归实现通过堆栈有效地避免了递归调用的限制,展示了如何用迭代方法解决原本看似需要递归的问题。

    快排与二分检索递归与非递归

    下面我们将深入探讨这两个算法的递归与非递归实现及其原理。 **快速排序** 快速排序是一种基于分治思想的排序算法,由C.A.R. Hoare在1960年提出。其基本步骤如下: 1. **选择枢轴元素(Pivot Selection)**:从待...

    二叉树递归与非递归遍历

    这三种遍历方式都分为递归和非递归两种实现方式。 **递归遍历** 1. **前序遍历**: - 访问根节点。 - 递归遍历左子树。 - 递归遍历右子树。 2. **中序遍历**: - 递归遍历左子树。 - 访问根节点。 - 递归...

    c语言,二叉树,前中后,递归,非递归

    根据给定的信息,本文将详细解释二叉树的遍历方法,包括递归与非递归方式下的前序、中序、后序遍历,并简要介绍层次遍历的概念。 ### 二叉树简介 二叉树是一种常用的数据结构,其中每个节点最多有两个子节点,通常...

    阿克曼函数非递归实现

    阿克曼函数是一种著名的计算上界递归函数,它在理论计算机科学中有着重要的地位,尤其是在探讨递归和计算复杂性的领域。这个函数是不可计算的,也就是说,它不能通过有限步骤的算法来解决,但是可以通过其他方法如...

    二分搜索的递归和非递归实现

    本篇文章将详细探讨二分搜索的递归和非递归实现。 首先,我们来看递归实现的二分搜索。递归是一种解决问题的方法,它将问题分解为更小的子问题,直到子问题可以直接解决。在二分搜索中,递归实现通常分为以下几步:...

    快速排序算法设计与分析总结 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现

    快速排序算法设计与分析总结 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现,应包含建树的实现 实现树与...

    算法分析 递归与分治策略 动态规划 贪心算法 分支限界法 随机化算法等算法

    在IT领域,算法是解决问题的核心工具,而递归与分治策略、动态规划、贪心算法、回溯法、分支限界法以及随机化算法都是其中的重要组成部分。这些算法不仅适用于计算机科学,也在数据结构、优化问题和复杂计算中扮演着...

    递归算法与非递归转化

    递归算法与非递归转化 递归算法是把问题转化为规模缩小了的同类问题的子问题,然后递归调用函数(或过程)来表示问题的解。递归的效率一般不高,但是递归比较符合人类的思维方式。一般而言非递归算法更有效;但很多...

    汉诺塔递归与非递归两种算法的代码与结果对比

    "非递归解决汉诺塔,每一步都有确切解.doc"文件很可能提供了非递归算法的详细步骤和解释,每一步都确保了盘子的合法移动,确保最终能将所有盘子从A移动到C。 **结果对比** "no differences"的结果表明,无论使用...

Global site tag (gtag.js) - Google Analytics