目录
对递归函数的理解
一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
递归的条件
每一个递归函数都应该只进行有限次的递归调用,否则它就会进入死胡同,永远也不能退出了,这样的程序是没有意义的。
- 存在限制条件,当符合这个条件时递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
代码实例演示
顺序打印整数每一位
要求:接收一个整形值,按照顺序打印他的每一位。例如:输入:1234,输出:1 2 3 4
解题关键:
当n>9时,递归打印(n/10),得到除个位之外的数。
当n<9时,n%10即是个位上的数字
代码演示:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void Print_num(unsigned int n)
{
if (n > 9)
{
Print_num(n /10);//递归打印,当满足n<9时结束递归,打印n%10;
}
printf("%d ", n % 10);
}
int main()
{
unsigned int n = 0;
scanf("%u", &n);//%u表示无符号整型
Print_num(n);
return 0;
}
求n的阶乘
要求:递归求n的阶乘
解题关键:
当n>1时,n!=n*(n-1)!;
当n<=1时,结果为1;
代码演示:
#include<stdio.h>
int Fac(int n)
{
int i = 0;
for (i = 1; i <= n; i++)
{
if (n <= 1)
{
return 1;
}
else
{
return Fac(n - 1) * n;
}
}
}
int main()
{
int n = 0;
scanf_s("%d", &n);
int ret = Fac(n);
printf("%d", ret);
return 0;
}
下面给出了逐层进入的过程:
逐层退出的过程:
求字符串长度
要求:不允许创建临时变量,求字符串长度
解题关键:
当字符!=‘\0’时,一个字符长度则是1,两个字符以上就是1+my_strlen(str+1)str+1指的是下一个字符的地址,不能用str++,因为传参时和下次调用时参数值不一样
代码演示:
//非递归实现
//#include<stdio.h>
//int main()
//{
// char arr[] = "hello";
// printf("%d", strlen("hello"));
// return 0;
//}
//递归实现
#include<stdio.h>
int my_strlen(char* str)
{
//不创建临时变量
/*if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
else
{
return 0;
}*/
//创建临时变量count
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "hello";
int ret = my_strlen(&arr);
printf("%d", ret);
return 0;
}
我这里给出了三种方法解题,大家可以参考参考,理解起来比较简单。
本篇重点:斐波那契数
要求:使用递归和非递归实现第n个斐波那契数的求法
解题关键:
n<=2时,结果都为1
n>2时,前两个数相加等于第三个数
代码演示:
#include<stdio.h>
int Fib(int n)
{
//非递归实现
int a = 1;
int b = 1;
int c = 1;
int i = 0;
for (i = 3; i <= n; i++)
{
c = a + b;
a = b;//下一次的a等于上一次的b
b = c;//下一次的b等于上一次的c
}
return c;
//递归实现
/*if (n <= 2)
{
return 1;
}
else
{
return Fib(n - 1) + Fib(n - 2);
}*/
}
int main()
{
int n = 0;
scanf_s("%d", &n);
int ret = Fib(n);
printf("%d", ret);
return 0;
}
字符串逆序排列
实现:编写一个函数 reverse_string(char * string)(递归实现)将参数字符串中的字符反向排列,不是逆序打印。
要求:不能使用C函数库中的字符串操作函数。
解题关键:
首先编写字符串长度函数,求出字符串长度
交换首元素和最后一个数,把中间的看成整体,只要长度大于等于2就可使用递归交换中间部分的数
要成功逆序打印,每次递归都要使最后一个数后面是\0,所以第一次递归时,应该把首元素先存放在一个临时变量里面,等所有元素递归完之后,再把首元素放回来
代码演示:
#include<stdio.h>
int my_strlen(char* str)//计算字符串长度
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
void Reverse_string(char* arr)
{
char temp = arr[0]; //将a用一个变量代替
int len = my_strlen(arr);
arr[0] = arr[len - 1]; //将f放到第一个位置
arr[len - 1] = '\0'; //这里是为了下一次递归调用时更将下一次字符的最后一个字符与第一的字符交换,便于表示;
if ((my_strlen(arr + 1)) >= 2) //这里的arr+1代表着第二次要逆序的字符,传参为首地址;
{ //只要长度大于2就可以交换;
Reverse_string(arr + 1);
}
arr[len - 1] = temp; //所有递归之后将第一个元素放到最后;
}
int main()
{
char arr[] = "abcdef";
Reverse_string(arr);
printf("%s\n", arr);
return 0;
}
总结及扩展
- 递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。
- 要想理解递归函数,重点是理解它是如何逐层进入,又是如何逐层退出的
- 递归的时候,每次调用一个函数,计算机都会为这个函数分配新的空间,这就是说,当被调函数返回的时候,调用函数中的变量依然会保持原先的值,否则也不可能实现反向输出。
- 函数调用的参数是通过栈空间来传递的,在调用过程中会占用线程的栈资源。而递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。比如用递归求第50个斐波那契数时,计算机大概需要5分钟,但是用循环求法,会很快。
- 函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N次局部变量、N次形参、N次调用函数地址、N次返回值,势必效率低。
- 递归代码简洁、清晰,易懂,循环能干的事,递归都能干;递归能干的循环不一定能干,对于我们,能用循环解决的,尽量不适用递归.