函数递归与迭代附n的阶乘+顺序打印一个整数的每一位数+求第n个斐波那契数

本文主要是介绍函数递归与迭代附n的阶乘+顺序打印一个整数的每一位数+求第n个斐波那契数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 什么是递归?

        递归其实是一种解决问题的方法,在C语言中,递归就是函数自己调用自己

下面是一个最简单的C语言递归代码:

#include <stdio.h>
int main()
{printf("hehe\n");main();//main函数中⼜调⽤了main函数return 0;
}

        上述就是⼀个简单的递归程序,只不过上面的递归只是为了演示递归的基本形式,不是为了解决问题,代码最终也会陷入死递归,导致栈溢出(Stack overflow)。

1.1递归的思想:

        把一个大型复杂问题层层转化为一个与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束了。所以递归的思考方式就是把大事化小的过程。
        递归中的递就是递推的意思,归就是回归意思,接下来慢慢来体会。

1.2 递归的限制条件

 从上面例子,我们也可以看出在书写递归的时候,有2个必要条件:

  • 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续,否则就会出现栈溢出的情况。
  • 每次递归调用之后越来越接近这个限制条件。

在下面的例子中,我们会进一步体会这2个限制条件。

 2.递归举例

2.1 举例1 :求n的阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。
自然数n的阶乘写作n!。
题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

2.1.1 分析和代码实现

我们知道n的阶乘的公式: n! = n *( n - 1)!

  1. 举例:
  2.         5! = 5*4*3*2*1
  3.         4! = 4*3*2*1
  4. 所以:5! = 5*4!

这样的思路就是把⼀个较大的问题,转换为一个与原问题相似,但规模较小的问题来求解的。
总结:当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。
n的阶乘的递归公式如下:


那我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,函数如下:

#include <stdio.h>
int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(n-1);
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fact(n);printf("%d\n", ret);return 0;
}

运行结果(这里不考虑n太大的情况,n太大存在溢出):

迭代方式:

#include<stdio.h>
int Fact(int n)
{int i = 0;int ret = 1;for(i=1; i<=n; i++){ret *= i;}return ret;
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fact(n);printf("%d\n", ret);return 0;
}

2.1.2 画图推演

 解释:

   首先传递参数5后,程序执行 n * Fact( n - 1)后调用自己并传参数4。一步一步递推到Fact(0),此时函数返回 1,即Fact(0)=1 。在这之后,程序开始回归,首先回归到Fact(1)= 1 * Fact(0),然后程序继续回归,直到Fact(5),所以最终计算出5的阶乘。

2.2 举例2 :顺序打印一个整数的每一位

输入⼀个整数m,打印这个按照顺序打印整数的每⼀位。
比如:
输入:1234      输出:1 2 3 4
输入:520        输出:5 2 0

2.2.1 分析和代码实现

这个题目,放在我们面前,首先想到的是,怎么得到这个数的每⼀位呢?
如果n是一位数,n的每一位就是n自己
n超过1位数的话,就得拆分每⼀位。1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4,然后继续对123%10,就得到了3,再除10去掉3,以此类推,不断的 %10 和 /10 操作,直到1234的每⼀位都得到;

但是这里有个问题就是得到的数字顺序是倒着的

但是我们有了灵感,我们发现其实一个数字的最低位是最容易得到的,通过%10就能得到
那我们假设想写一个函数Print来打印n的每⼀位,如下表示:

Print(n)
如果n是1234,那表⽰为
Print(1234) //打印1234的每⼀位
其中1234中的4可以通过%10得到,那么
Print(1234)就可以拆分为两步:
1. Print(1234/10) //打印123的每⼀位
2. printf(1234%10) //打印4
完成上述2步,那就完成了1234每⼀位的打印
那么Print(123)又可以拆分为Print(123/10) + printf(123%10)

以此类推下去,就有

        Print(1234)
==>Print(123)                 +               printf(4)
==>Print(12)         +         printf(3)
==>Print(1)     +    printf(2)
==>printf(1)

直到被打印的数字变成一位数的时候,就不需要再拆分,递归结束。
那么代码完成也就比较清楚:

#include <stdio.h>
void Print(int n)
{if(n>9){Print(n/10);}printf("%d ", n%10);
}
int main()
{int m = 0;scanf("%d", &m);Print(m);return 0;
}

输入和输出结果:

在这个解题的过程中,我们就是使用了大事化小的思路
把Print(1234)打印1234每⼀位,拆解为首先Print(123)打印123的每⼀位,再打印得到的4
把Print(123)打印123每⼀位,拆解为首先Print(12)打印12的每⼀位,再打印得到的3
直到Print打印的是⼀位数,直接打印就行。

2.2.2画图推演

以1234每⼀位的打印来推演⼀下

 解释:

        整体来说先执行银色的,再执行黄色的线,即先递推再回归。当调用传的参数是一位数即1的时候打印1,随即回归依次打印2 3 4

 3.递归与迭代

递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误用的,就像举例1一样,看到推导的公式,很容易就被写成递归的形式:

int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(n-1);
}

Fact函数是可以产生正确的结果,但是在递归函数调用的过程中涉及一些运行时的开销。
在C语言中每⼀次函数调用,都要需要为本次函数调用在栈区申请⼀块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧
函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。
所以如果不想使用递归就得想其他的办法,通常就是迭代的方式(通常也就是循环的方式)。
比如:计算n的阶乘,也是可以产生1~n的数字累计乘在⼀起的。

int Fact(int n)
{int i = 0;int ret = 1;for(i=1; i<=n; i++){ret *= i;}return ret;
}

上述代码是能够完成任务,并且效率比递归的方式更好。
事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰,但是这些问题的迭代实现往往比递归实现效率更高,如计算第n个斐波那契数。
当⼀个问题非常复杂,难以使用迭代的方式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

举例3:求第n个斐波那契数

我们先来了解一下斐波那契数:

        斐波那契数列:1,1,2,3,5,8,13,21,34,55,89…… ,

        以递归的方法定义:从第三项开始,每一项都等于前两项之和,显然这是一个线性递推数列。

就像计算第n个斐波那契数,是不适合使用递归求解的,但是斐波那契数问题的通过是使用递归的形式描述的,如下:

看到这公式,很容易诱导我们将代码写成递归的形式,如下所示:

int Fib(int n)
{if(n<=2)return 1;elsereturn Fib(n-1)+Fib(n-2);
}

测试代码:

#include <stdio.h>
int main()
{int n = 0;scanf("%d", &n);int ret = Fib(n);printf("%d\n", ret);return 0;
}

当我们n输入为50的时候,需要很长时间才能算出结果,这个计算所花费的时间,是我们很难接受的,这也说明递归的写法是非常低效的,那是为什么呢?

其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计算,而且递归层次越深,冗余计算就会越多。我们可以测试:

#include <stdio.h>
int count = 0;
int Fib(int n)
{if(n == 3)count++;//统计第3个斐波那契数被计算的次数if(n<=2)return 1;elsereturn Fib(n-1)+Fib(n-2);
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fib(n); printf("%d\n", ret);printf("\ncount = %d\n", count);
return 0;}

运行结果:

这里我们看到了,在计算第40个斐波那契数的时候,使用递归方式,第3个斐波那契数就被重复计算了39088169次,这些计算是非常冗余的。所以斐波那契数的计算,使用递归是非常不明智的,我们就得想迭代的方式解决。
我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从小到大计算就行了。
这样就有下面迭代的代码:

int Fib(int n)
{int a = 1;int b = 1;int c = 1;while(n>2){c = a+b;a = b;b = c;n--;}return c;
}

迭代的方式去实现这个代码,效率就要高出很多了。

期待

 

您的认同给予了我莫大的鼓励!!!

这篇关于函数递归与迭代附n的阶乘+顺序打印一个整数的每一位数+求第n个斐波那契数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/716401

相关文章

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

Python实现阶乘的四种写法

《Python实现阶乘的四种写法》本文主要介绍了Python实现阶乘的六种写法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录第一种:推导式+循环遍历列表内每个元素相乘第二种:调用functools模块reduce的php累计

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日