从斐波那切数列看递归和动态规划的区别

0816更新

对于递归的补充,同样来自邓俊辉老师《数据结构C++》。

  • 分支转向是算法的灵魂。
  • 函数和过程及其之间的相互调用,是在经过抽象和封装之后,实现分支转向的一种重要机制
  • 而递归是这种重要机制的特殊形式,因为允许函数和过程进行自我调用。具有高度的抽象性和简捷性
  • 递归
    • 递归的价值在于许多问题都可简介而准确的描述为递归形式
    • 也是一种基本而典型的算法设计模式。可以对问题中反复出现的结构和形式作出高度概括,并从本质上加以描述和刻画,进而导出高效的算法
  • 递归的复杂度计算
    • 递归跟踪
    • 递推方程
  • 递归的代价

递归可以让我们在宏观上理解和把握问题的实质,深入挖掘和洞悉算法过程中的主要矛盾和一般性模式,并最终设计出简洁优美而精确紧凑的算法,然而众多优点的背后,隐含着某些代价

  • 空间成本:
    所消耗的空间量主要取决于递归深度,故较之同一算法的迭代版,消耗更多的空间,并进而影响实际的运行速度。如操作系统而言,为实现递归调用需要花费大量的时间创建维护销毁各种递归实例,这也会令计算雪上加霜。
  • 解决方案:
    一般的转换思路为:利用栈结构模拟操作系统的工作过程。
    简单而常见的情况为:将尾递归转换为对应的迭代版本
  • 递归的不同形式

    • 线性递归:每次对自身调用至多一次
      • 问题分解为两个独立的子问题:一个对应单独的某个元素,可直接求解。另外一个为剩余部分,结构与原问题相同
      • 对应着 减而治之的算法策略decrease-and-conquer:递归每深入一层,待求解的问题的规模都将所见一个常数,直至最终退化为平凡的小(简单)问题。
      • 若递归调用在递归实例中恰好最后一步操作的形式出现,则称为尾递归。注意是最后一步的操作,是实际的执行过程中,而并非语法的外在形式。尾递归都可以简捷的转换为等效的迭代算法
  • 二分递归:每一递归实例可能做多次递归,称为多路递归,通常将问题一分为二,故称为二分递归

    • 对应着分而治之(divede-and-conquer)的策略,分解为若干个规模更小的子问题,(最好子问题都是独立的子问题,这样可以独立求解,而无需借助其他子问题的原始数据或中间结果。否则子问题之间相互传数据或者互相调用,导致了时间复杂度和空间复杂度的无谓增加,而对于子问题的重叠 【overlapping】/重复的递归实例,有对应的优化策略)
  • 举例 数组求和

    • 线性递归版 (注意不是尾递归,最后一步是加法,虽然语法形式是递归出现在最后一步)
//线性递归版本

int sum(int A[ ],int n){
    if (n<1)  // 平凡情况,递归基  可否理解为递归出口
       return 0;
    else   // 一般情况
       return sum(A,n -1 )+ A[n -1];
    
}
  • 二分递归版本:以中间的元素将数组一分为二
int sum(int A[ ],int lo,int hi){
    if(lo ==hi) return A[lo];
    else{
        int mi=(lo+hi)>>1;
        return sum(A,lo,mi)+sum(A,mi+1,h1);
           }
}
  • 比较 :
    • 时间复杂度 o(n) 相同
    • 空间复杂度 线性的为 o(n) 二分的为 o(logn) (递归深度-任一时刻活跃的递归实例的总数)

0815更新部分

今天看到了邓俊辉老师《数据结构C++》第一张的1.4节中 递归的优化策略的描述,又犯了嘀咕了,暂且先记录下来。

为了消除递归算法中重复的递归实例,一种自然而然的思路和技巧。可以概括为:
借助一定量的辅助空间,在各子问题求解之后,及时记录下其对应的解答
比如,可用从原问题出发自顶而下,每遇到一个子问题,都先查验它是否已经计算过,以期通过直接调阅记录获得解答,从而避免重复计算。
也可以从递归基出发,自底而上的递出各个子问题的解,直至最终原问题的解。
前者所谓的制表(tabulation)或记忆(memoization)策略,后者即所谓的动态规划策略

之前的理解制表和记忆是不等价的,还有待查阅更多资料。

不过看到邓公课件上写的 make it work,make it right,make it fast. 可以先用递归去思考问题,初步试验让其工作,为了提高效率,在用迭代去优化。

可能不应该太拘泥于这些概念的区别,应该知道如何把问题归类,找到更适合的方法去解决




【0814】

结论:

  • 动态规划是一种思维方式,把大问题分解成若干个子问题,且子问题有重复,每个子问题只计算一次保存其结果,这样下次遇到相同的子问题就直接用之前保存好的答案,最后合并子问题的解。(为什么叫动态这个名词,详见参考链接,故事还挺有意思)
  • 递归是一种编程方式,函数调用其本身,不论是直接还是间接。
  • 利用 递归 + 记忆化搜索 可以实现 自上而下的 动态规划
  • 利用 迭代,先解决小问题,可以实现 自下而上的 动态规划

动态规划问题的两种类型

  • 优化问题 如求最值
  • 组合问题 做某件事的数量 或者 发生的概率

动态规划的解决方案

  • 自上而下 :假设子问题已经解决

自上而下:你从最顶端开始不断地分解问题,直到你看到问题已经分解到最小并已得到解决,之后只用返回保存的答案即可。这叫做记忆存储(Memoization

  • 自下而上 :先解决子问题

自下而上:你可以直接开始解决较小的子问题,从而获得最好的解决方案。在此过程中,你需要保证在解决问题之前先解决子问题。这可以称为表格填充算法(Tabulation,table-filling algorithm**)。

动态规划与分冶治之

斐波那切数列

  • 方案一:暴力递归


    相同颜色的为重复子问题
int num=0;             // count the number of fib call
vector <int> memo;     // save the result of  subquestion 

int fib_recur(int n ){

   num++;
   if (n == 0) return 0;
   if (n == 1) return 1;
   return fib_recur(n-1)+fib_recur(n-2);

}
  • 方案二:递归加记忆化搜索
int fib_recur_memo(int n ){
    
    num++;

    if (n == 0) return 0;
    if (n == 1) return 1;

    if(memo[n]== -1)  // not see the situation befroe ,the memo it
      memo[n] =fib_recur_memo(n-1)+fib_recur_memo(n-2);
    
    return memo[n]; // if see it before , just look up and return
}

  • 方案三:自下而上的迭代动态规划
int fib_recur_memo(int n ){
    
    num++;

    if (n == 0) return 0;
    if (n == 1) return 1;

    if(memo[n]== -1)  // not see the situation befroe ,the memo it
      memo[n] =fib_recur_memo(n-1)+fib_recur_memo(n-2);
    
    return memo[n]; // if see it before , just look up and return
}
  • 讨论
    输入为30的结果比较

1.加记忆化的递归 调用自身的次数明显减少 ,为2n-1, 时间复杂度和 自下而上的迭代动态规划相同

2.自下而上的迭代 和 自上而下的 记忆的 本质区别在哪呢?

  • 前者想一个无微不至(eager)的老管家,把所有一切都给你准备好了,不管你用的用不着。

  • 后者是比较懒惰的(lazy)的管家,把你平常需要准备好了,如果需要的没有再去准备好了。

其他

动态规划和分冶策略

image.png

参考

完整代码

/* 斐波那契数列问题
- F(0)=1, F(1)=1,F(n)=F(n-1)+F(n-2)
递归方法   fib_recur
发现有重叠的子问题
  1. 记忆化搜索 / 递归 (自上而下)  fib_recur_memo
  2. 迭代    (自下而上)   fib_dp
*/


#include <iostream>
#include <vector>
#include <time.h>

using namespace std;

int num=0;             // count the number of fib call
vector <int> memo;     // save the result of  subquestion 

int fib_recur(int n ){

   num++;
   if (n == 0) return 0;
   if (n == 1) return 1;
   return fib_recur(n-1)+fib_recur(n-2);

}

int fib_recur_memo(int n ){
   
   num++;

   if (n == 0) return 0;
   if (n == 1) return 1;

   if(memo[n]== -1)  // not see the situation befroe ,the memo it
     memo[n] =fib_recur_memo(n-1)+fib_recur_memo(n-2);
   
   return memo[n]; // if see it before , just look up and return
}

int fib_dp(int n){
   
   num++;
   vector<int> memo (n+1,-1);

   memo[0]=0;
   memo[1]=1;

   for(int i =2;i<=n;i++) {
       memo[i]=memo[i-1]+memo[i-2];
     }

   return memo[n];
  
}


int main(){

   int n = 30;
   memo=vector<int> (n+1,-1);

   time_t startTime =clock();
   int res=fib_dp(n);   // change different func when u need to compare 
   time_t endtime=clock();
   double usedtime=double(endtime-startTime)/CLOCKS_PER_SEC;

   printf("fib(%d)=%d\n",n,res);
   printf("used time : %f \n",usedtime);
   printf("the number of fib called : %d",num);

   return 0;

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容