[算法系列]一:递归类问题 从斐波那契数列开始

递归类问题

递归类问题指后续步骤都是有基本步骤叠加而成的问题。一般问题设定为做某事有n种方法x,y。。。,每次只能用其中一种,次数不限,问共有多少种方法排列组合可以造成结果R?

这类问题思想很经典,可以把问题分解成多个本原问题,变成递归问题,再将递归优化为非递归算法,我们从斐波那契数列开始讲

fibonacci数列:

f(x)=\begin{cases}0,& x=0\\1,& 0< x\leq 2\\ f(x-1)+f(x-2),& x>2 \end{cases}
可以理解为每次可以从x=0, x=1, x=2三件事里挑一件做,其中结果加0会导致问题闭环,有无穷解,故一般问题不会有这种条件。所以我们的问题x为正整数。

现在问题就变成了:一共做了q次事,每次从x=1 和x=2里挑一件做,共有多少种组合?(求f(q))

递归算法:

 def fibonacci(n):
     if n==1 or n==2:
         return 1
     else:
         return fibonacci(n-1)+fibonacci(n-2)

算法分析

只要函数未到达递归出口(n<=2),就会不断的在现有函数栈上开辟新的空间(形参表、静态链、动态链、返回地址)。所以当递归太深的时候会出现栈溢出(stack overflow)
可以看出n>2后,每算一个数,都要递归调用前两个数的和,而前两个也要继续向前递归,每次递归相当于是重新在原有的函数栈帧上再次开辟空间来运行函数.

递归二叉树

二叉树的高度是 n - 1,高度为k的二叉树最多可以由 2^k - 1个叶子节点,即调用2^n - 1次,所以时间复杂度为 O(2^n),而空间复杂度就是树的高度 O(n)
算法指数级的时间复杂度,随着参数的增长很容易出现栈溢出。

一般我们认为常数、线性、多项式级别的复杂度可以忍受,而指数级的复杂度随着规模增大,计算效率急剧下降,无法应用到实际问题中。相应地,不存在多项式复杂度算法的问题,被称作难解的(intractable)问题。

非递归算法

每算一个数都要把前面的所有数重算一次,其实从头开始算,保存下末尾的两个数就好了,把递归变成迭代
优化的方法很简单,从头算就行了,每次算出的数存到队列尾部,下一个要计算的数等于末尾两个的和嘛,这样算过的就不用再算了。。。可以直接取到。。。

def fib(n):
    a=time.time()
    for i in range(n):
        if i==0:
            stack.append(1)
        elif i==1:
            stack.append(1)
        else:
            stack.append(stack[-1]+stack[-2])
    a=time.time()-a
    print('非递归结果🐶:'+str(stack[-1])+' 用时:'+str(a)+'s\n')

时间复杂度O(n),空间复杂度O(n)
下面是对比

n 递归算法O(2^n)(计算次数/时间) 非递归算法O(n)(计算次数/时间)
10 计算109次 用时:5.60e-05s 计算10次 用时:9.79e-05s
20 计算13529次 用时:0.0049s 计算20次 用时:4.31e-05s
30 计算1664079次 用时:0.27s 计算30次 用时:9.51e-05s
40 计算204668309次 用时:32s 计算40次 用时:5.19e-05s

递归算法n=50就要等待n=40的2^{10}倍,也就是512分钟,9个小时,而非递归只需要1e5级别的时间,万分之一秒内完成。算法何必为难算法。。。

汉诺塔也属于这类问题

例题 HDOJ2041 2044 2045 2046

HDOJ 2041

问题链接2041
Problem Description

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 87980    Accepted Submission(s): 45195

有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?

Input

输入数据首先包含一个整数N,表示测试实例的个数,然后是N行数据,每行包含一个整数M(1<=M<=40),表示楼梯的级数。

Output

对于每个测试实例,请输出不同走法的数量

Sample Input
2
2
3

Sample Output
1
2

问题分析:每次只能向上跨一级或者两级,那么到M级只有两种方法,从M-1级跨一级或者从M-2级跨两级。那么跨一级次数加一,跨两级次数也是加一。把树画出来就发现是个递归了。
f(1)=1 跨一步算一次\\ f(2)=1跨两步算一次\\ f(m)=f(m-1)+f(m-2)
就是一个fibonacci数列。。。
要注意三个问题

  • 1.类型范围 这一题int就够了,如果级数增加可能就需要long 等等了
  • 2.复杂度 上面我们分析过,递归必然超时,需要改成迭代或者栈方法
  • 3.输出记得换行

ACcode

#include <iostream>
#include <stack>
#include <string>
using namespace std;
int fib(int i){
int sum=0,start=0,end=0;
for (int n=1;n<=i;n++){
    if (n==1){
        end=1;
    }
    else if(n==2){
        start=1;
    }
    else {
        sum=start+end;
        start=end;
        end=sum;
    }
}
return end;
}
int main(){
int num=0,Destination=0;
scanf("%d",&num);
for (int i=0;i<num;i++){
    scanf("%d",&Destination);
    printf("%d\n",fib(Destination));
}
return 0;
}

hdoj2044

hdoj2044问题链接

问题描述


image.png

问题分析:小蜜蜂在格子每次只有两个选择:1.直接向右走一步,序号加二 2.斜着走一步序号加一

f(1)=1 直接走算一次\\f(2)=1斜着走算一次\\f(M)=f(M-1)+f(M-2)
注意问题:

  • 1 从a到b相当于从1到a-b+1。
  • 2 b大于40,2^{40} int会越界,要用long long

ACcode

#include <iostream>
long long go(long long n){
long long  step1=0,step2=0,sum=0;
    for (long long  i=1;i<=n;i++){
        if(i==1){
            step2=1;
        }
        else if (n==2){
            step1=1;
            step2=1;
        }
        else{
            sum=step1+step2;
            step1=step2;
            step2=sum;
            }
    }
    return step2;
}
int main(){
int num=0;
long long  start=0,end=0;
scanf("%d",&num);
for (int i=0;i<num;i++){
    scanf("%lld %lld",&start,&end);
    printf("%lld\n",go(end-start+1));
}
return 0;
}

hdoj2045 RPG难题

hdoj2045地址http://acm.hdu.edu.cn/showproblem.php?pid=2045

image.png

问题分析:问题重点在于正确推出递推公式,推出来就和就是简单的递推了。

f(1)=3\\f(2)=6\\f(3)=6\\接下来分析f(4)的时候就发现,f(4)受前一个的结果影响。\\如果第n-1个格子与第1个格子颜色相同,那么第n个格子可以取两个颜色,此时有2*f(n-2)种\\(为什么不是2*f(n-1)?)因为格子n-1此时只能选和第1个一样的一个\\而如果第n-1个格子和第1个格子颜色不同,第n个格子将会只有1种选择此时有f(n)种\\ 所以n>3时有 f(n)=2*f(n-2)+f(n-1)
ACcode

#include <iostream>
#include <cstdio>
using namespace std;
long long fib(long long n){
        long long temp=0,step1=0,step2=0;
        for (long long i =1;i<=n;i++){
                if (i==1){
                        step2=3;
                }
                else if(i==2){
                        step1=3;
                        step2=6;
                }
                else if(i==3){
                        step1=6;
                        step2=6;
                }
                else{
                        temp=step2;
                        step2=2*step1+step2;
                        step1=temp;
                }
        }
        return step2;
        }

int main(){
long long num=0;
while(scanf("%lld",&num)!=EOF){
        printf("%lld\n",fib(num));
}
return 0;
}

hdoj 2046

骨牌铺方格http://acm.hdu.edu.cn/showproblem.php?pid=2046


问题分析:看起来问题变成二维的了,其实不然。骨牌只有两个并列横着放和单个竖着放两种情况。并列横着放方格长度+2,竖着放长度+1.问题就变成了每次可以走一米或者走两米,问走n米有多少种走法。也是个fibonacci问题。

f(1)=1一块骨牌竖着摆\\f(2)=2 两块骨牌横着放\\f(m)=f(m-1)+f(m+1)
问题就变得简单起来了。
ACcode

#include<iostream>
#include<cstdio>
long long queue[51]={1,2};
long long go(long long n){
    if (n==1)
        return 1;
    if (n==2)
        return 2;
for (int i=2;i<n;i++){
    queue[i]=queue[i-1]+queue[i-2];
}
return queue[n-1];
}
int main(){
long long n=0;
while(~scanf("%lld",&n)){
printf("%lld\n",go(n));
}
return 0;
}

hdoj2047 EOF牛肉串

http://acm.hdu.edu.cn/showproblem.php?pid=2047


问题分析:每次可以从E、O、F三个字符串种选一个刻到牛肉串上,长度加1,两个o不能连续。问刻n个字符有多少种刻法?

首先f(1)=3\\f(2)=8\\如果第n个字符为o,那么第n-1就只能取E、F两种,有2*f(n-2)种\\如果第n个字符串不为o,可以取E、F,则第n-1个字符就没有被n限制,有2*f(n-1)种\\所以f(n)=f(n-1)+f(n-2)
ACcode

#include <iostream>
#include <cstdio>
long long chuan[41]={3,8};
long long cow(long long n){
    if (n==1)
        return 3;
    if (n==2)
        return 8;
    for (int i=2;i<n;i++){
    chuan[i]=2*chuan[i-2]+2*chuan[i-1];
    }
return chuan[n-1];
}
int main(){
long long n=0;
while(~scanf("%lld",&n)){
    printf("%lld\n",cow(n));
}
return 0;
}

fibonacci 测速小脚本

import time
stack=[]
#非递归写法:(栈)
numre=0
numinre=0
def fib(n):
    global numinre
    a=time.time()
    for i in range(n):
        if i==0:
            stack.append(1)
        elif i==1:
            stack.append(1)
        else:
            stack.append(stack[-1]+stack[-2])
        numinre+=1
    a=time.time()-a
    print('非递归结果🐶:'+str(stack[-1])+' 计算'+str(numinre)+'次'+' 用时:'+str(a)+'s\n')
#递归写法
def fibonacci(n):
    global numre
    numre+=1
    if n==1 or n==2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)
if __name__=='__main__':
    print('🐷🐴fibonacci递归与非递归算法速度对比\n')
    x=int(input('🐶请输入n:'))
    fib(x)
    t=time.time()
    re=fibonacci(x)
    t=time.time()-t
    print('🐷递归结果🐷:'+str(re)+' 计算'+str(numre)+'次'+' 用时:'+str(t)+'s\n')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容