C语言 函数与递归(其三)
本期是我们函数部分的最后一节,其实我们在上两节已将函数的大致内容介绍完毕了,本节我们主要来介绍递归的知识。由于本节知识点较少,而需要大家动手操作的地方较多,我们主要以举例子的方式来介绍,接下来我们开始本期的学习。
本期我们主要学习函数递归相关的知识
函数递归
什么是递归?
程序调用自身的编程技巧称为递归(recursion)。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来解决,递归策略只需少量的程序就可描述出解题过程所需的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式为:把大事化小
递归的两个必要条件
1.存在限制条件,当满足这个限制条件时,递归便不再继续。
2.每次递归调用之后逐渐接近此限制条件。
所有函数递归都需要满足上述两个条件,否则函数就会出现问题,例如下面的程序:
int main(){ printf("hehe\n"); main ();//调用自身————递归 return 0;}
如果我们写出了这样的递归后去运行,我们就会发现程序报错,且有关键词“stack overflow”,这就是递归常见的错误 “栈溢出” 。这里简单地介绍一下什么是栈溢出。
我们在执行程序时需要向内存申请空间,空间主要被分成三部分:栈区,堆区,静态区。在函数调用时是从栈区申请空间的,上述代码由于“main”函数重复调用自身而没有结束限制条件,违背了上面提到的递归的两个条件,导致栈区空间被使用完,造成栈溢出。
用最通俗的话来说,如果我们前面讲过的函数嵌套是一个函数调用另一个函数的话,那么函数递归就是函数通过自身调用来实现程序,下面我为大家举几个递归相关的例子
例一:
【 接收一个整型值,按照顺序打印它的每一位。】
例如:输入1234,打印出1 2 3 4
#include <stdio.h>void print(int n)//函数只是执行操作,不需要有返回值,所以返回值类型是“void”{ if(n>9) { print(n/10);//递归的具体实现 } printf("%d ",n%10);}int main (){ int a=0; scanf_s("%d",&a); print(a);//调用该函数 return 0;}
如此我们就成功地完成了任务,上述代码“print”通过不断调用自身来完成了打印整型值的任务,那我们看一下此递归是否满足了我们上面提到的两个必要条件,只要“n<9”,函数便不再递归,明显满足限制条件。
我们接下来再看一个例子:
例二:
【 在不创建临时变量的情况下编写一个函数求一字符串的长度。】
这道题可能刚入手有些发懵,我们先看一下在创建临时变量是如何求字符串长度的:
#include <stdio.h>int strlen(char* str){ int count=0;//临时变量 while(*str != '\0') { count++; str++; } return count;}int main(){ char arr[]="bit"; int len=strlen(arr); printf("len=%d\n",len); return 0;}
上述代码虽正确,但使用了临时变量,接下来我们尝试使用本节学习的递归知识来解决此问题:
int strlen(char* str){ if(*str != '\0'); { return (1+strlen(str+1));//仔细体会此处递归的用法 } else return 0;}int main(){ char arr[]="bit"; int len=strlen(arr); printf("len=%d\n",len); return 0;}
总而言之,递归其实就是函数通过调用自身来解决一些复杂的问题,它的原理非常简单,但想熟练使用却需要我们大量的练习。
在我们解决问题时,相较于不使用递归,递归的代码量会显得非常简洁,接下来的例子为大家展示递归函数的简洁性。
例三:
【求n的阶乘】
这里我先使用迭代方式来编写程序:
#include<stdio.h>int fac1(int x){ int i=0; int ret=1; for(i=1;i<=x;i++) { ret=i*ret; } return ret;}int main(){ int a=0; scanf_s("%d",&a); int ret=fac1(a); printf("%d",ret); return 0;}
上述代码使用了迭代的形式来编写代码,所谓迭代,其实就是函数内部的循环结构。我们在编程时使用迭代也能解决问题,但单单从代码的简洁性上来说,迭代是远远比不上递归的。接下来我们使用递归来解决上述问题:
#include<stdio.h>int fac2(int x){ if(x<=1) return (x*fac(x-1));}int main(){ int a=0; scanf_s("%d",&a); int ret=fac2(a); printf("%d",ret); return 0;}
如上述代码,我们仅用了一个if语句就完美地解决了问题,而上述的迭代却定义了两个临时变量加上使用了一个for循环才解决问题,递归写法的简洁性可见一斑了。
但并不是所有的代码都适合用递归来写,请看下面的例子:
例四:
【求第n个斐波那契数】
所谓斐波那契数,就是从1,1开始,一个数等于前两个数之和。
如 1,1,2,3,5,8,13,21......
其实此问题很好解决:
int fib(int n){ if (n<=2) return 1; else return fib(n-1)+fib(n+1);}int main (){ int a=0; int ret = 0; scanf_s("%d",&a); ret=fib(n); printf("%d ",ret); return 0;}
经过测试,代码是可以完成任务的,但当我们想求第50个以上的斐波那契数时,我们会发现程序会运算很长时间,这时我们就会发现一个问题:在使用迭代运行程序时会造成计算力的大量浪费,这时使用递归就不是明智之举了,接下来我们使用迭代来编写该代码:
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 0;}int main (){ int a=0; int ret = 0; scanf_s("%d",&a); ret=fib(n); printf("%d ",ret); return 0;}
这样程序就可以顺利运行了,我们这时就可以求可在int范围内存储的任意斐波那契数了。
到此,我们本节内容就结束了。递归和迭代这两种方法各有利弊,我们要灵活使用,我们下期将继续介绍C语言相关的知识。

