递归和尾递归的运行流程解释

递归和尾递归的运行流程解释

递归定义


递归(英语:recursion)在计算机科学中是指一种经过重复将问题分解为同类的子问题而解决问题的方法。[1] 递归式方法能够被用于解决不少的计算机科学问题,所以它是计算机科学中十分重要的一个概念。[2] 绝大多数编程语言支持函数的自调用,在这些语言中函数能够经过调用自身来进行递归。计算理论能够证实递归的做用能够彻底取代循环,所以有不少在函数编程语言(如Scheme)中用递归来取代循环的例子。(摘自维基百科)编程

尾递归定义


在计算机学里,尾调用是指一个函数里的最后一个动做是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。[1]此时,该尾部调用位置被称为尾位置。尾调用中有一种重要而特殊的情形叫作尾递归。通过适当处理,尾递归形式的函数的运行效率能够被极大地优化。[1]尾调用原则上均可以经过简化函数调用栈的结构而得到性能优化(称为“尾调用消除”),可是优化尾调用是否方即可行取决于运行环境对此类优化的支持程度如何。(摘自维基百科)性能优化

前提知识


  • 递归我将它分为两个过程,一个我将它称为递归,另外一个我将它称为回溯. 递归的函数的运行主要有这两个流程,递归的进入,回溯的退出,这两个过程的分界是以递归出口为分界的.
  • 递归的实现形式是使用,递归函数的进入(递归)相似与压栈,递归函数的退出(回溯)相似于出栈.

递归样例和解释

【编程题】幂运算三(递归函数
题目ID:1137
【问题描述】 求x^n。
【输入形式】一行2个数,第一个整数表示x,第二个大于等于零的整数表示n,二数之间用空格分隔。
【输出形式】一行一个整数,表示x的n次方
【样例输入】2 3
【样例输出】8编程语言

【样例说明】2的3次方结果为8
【评分标准】5组测试用例,每组2分,共计10分 函数

【测试用例】
1)
输入:
2 3
输出:
8
2)
输入:
3 5
输出:
243性能

3)
输入:
-17 4
输出:
83521测试

4)
输入:
22 0
输出:
1优化

5)
输入:
-1287 0
输出:
1spa

//普通递归
#include<stdio.h>
long my_pow1(long x,int n){
    if(n==0) return 1;      //递归出口
    return x*(my_pow1(x,--n));  //除了调用自身外还乘多了个x,即通常的递归
}
int main(){
    long  x;
    int  n;
    scanf("%ld%d",&x,&n);
    printf("%ld\n",my_pow1(x,n));
    return 0;
}
  • 运行图解

图片描述
解释:
普通的递归过程是在一个函数中,结果依靠自身的调用来得出,例如求幂运算,pow(2,3)表明求2的3次方,因为pow(2,3)未知,咱们能够把它分解成2*pow(2,2),pow(2,2)也未知,又可分解成2*pow(2,1),以此类推,直到pow(2,0)可知(if中定义0时返回1),即pow(2,0)返回值是1.
在这个递归过程当中,pow函数的创建就是一个个压栈的过程
我把它称为函数栈3d

clipboard.png
压栈压入因此函数后,直到最后一个,能够得到最后一个函数的返回值,由这个返回值能够依次推出栈内全部函数的返回值(回溯),即退栈,pow(2,0)返回1,推的pow(2,1)返回2*pow(2,0),即2*1=2,pow(2,2)返回2*pow(2,1),即2*2=4,直到退到栈内最后一个函数pow(2,3),可得到pow(2,3)的返回值为2*pow(2,2)即8;code

尾递归样例和解释


【编程题】吃糖(尾递归函数
题目ID:1135
【问题描述】小樱是个爱吃糖的女孩, 哥哥送了她n(1<=n<=30)颗糖,怎么吃?一天吃1颗;一天吃2颗。嗯,那就天天吃一颗或两颗吧。
1颗糖,确定只有(1)一种吃法;2颗糖,有(1,1)和(2)两种吃法;3颗糖,有(1,1,1)、(1,2)和(2,1)三种吃法。注 (2,1)表示第一天吃2颗,次日吃1颗。*
你能帮小樱算出,吃完这n颗糖,有多少种吃法吗?请编写一个尾递归函数来解决此问题
【输入形式】

clipboard.png

【测试用例】
1)
输入:
1
输出:
result=1

2)
输入:
4
输出:
result=5

3)
输入:
15
输出:
result=987

4)
输入:
20
输出:
result=10946

5)
输入:
30
输出:
result=1346269

实际上这道题是一个斐波那契数列的变体,可用尾递归函数解决

//尾递归
#include <stdio.h>
int ci(int n,int pre,int next)
{
    int sum;
    if (n==1){              //递归出口(递归和回溯的分界点)
        return pre;
    }
    return ci(n-1,next,pre+next);  //除了调用自身外没有其余操做即为尾递归
}
int main()
{
    int n;
    int sum;
    scanf ("%d",&n);
    printf ("result=%d",ci(n,1,2));
    return 0;
}

运行图解
图片描述

解释:

  • 尾递归是属于递归的,它也有递归压栈两个过程,可是因为尾递归除了调用自身外没有任何其余多余的操做,因此它在进行到递归出口的时候,得到一个返回值,进行回溯的每个函数的返回值都是它的调用的另外一个规模缩小的函数,也就是说:函数在压栈的时候,压到栈顶函数,得到一个返回值,退栈,当前栈顶得到返回值,这个值是退栈的那个元素的返回值,依次类推,直到全部函数退栈,得到的返回值都是一个值,也就是递归出口的那个值.
  • 用例子来表示,便是回溯过程当中,ci(1,5,8)得到了返回值5,ci(2,3,5)的返回值是ci(1,5,8),也就是5,ci(3,2,3)的返回值是ci(2,3,5),也就是5,ci(4,1,2)的返回值也是5;
  • 从尾递归的流程中能够看出,尾递归的回溯的过程是彻底没用的,直到递归出口就已经得到了想要的值了.
  • 递归的过程也只是至关于循环而已,ci(4,1,2),4是要求斐波那契变体的第4项,1是第一项,2是第二项,从4减到1起到一个计数做用,1和2也是做为递推的起点
  • 因此,在不少语言中,尾递归可被优化成和循环(递推)同样的效率.

总结

  • 递归有两个过程,递归和回溯,递归到最深处得到一个返回值,由这个返回值回溯推出想要求的函数的返回值,起到缩小规模计算的做用.
  • 尾递归与递归的不一样处在于尾递归在return处除了调用自身外没有其余操做,致使回溯的过程是彻底浪费的.
  • 尾递归在一些语言中能够优化成和循环同样的执行效率.

*下图是普通递归和尾递归的执行图示:
图片描述
图片描述

相关文章
相关标签/搜索