目录
一、什么是递归
程序调用自身的编程技巧称为递归(recursion)
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归策略——只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小。
按我的理解来说:递——递推;归——回归,有公式就非常好写,没有公式就自己推导
二、递归的两个必要条件
1. 两个必要条件
● 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
● 每次递归用完之后,越来越接近这个限制条件。
▼接下来我会举两个例子来进一步地讲解。
2. 练习(1)
● 题目:接受一个整型值(无符号),按照顺序打印它的每一位
输入:1234 输出:1 2 3 4、
▼思路:我们就用Print函数用来实现吧,Print函数无返回值,用来打印一个整型值的每一位。
Print(1234) → Print(123) 4 → Print(12) 34 → Print(1) 234 → Print()1234
相当于我们反复调用Print(n/10),直到n不是两位数,并且每次调用后都打印n%10的结果
即n>9时,我们执行Print(n/10)后执行printf("%d ", n%10),下面是参考代码。
#include <stdio.h>
void Print(int n)
{
if (n > 9)
{
Print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 1234;
Print(num);
return 0;
}
♦ 第一次调用:Print(1234)中,n=1234,满足n>9,所以先调用函数Print(123),下面的代码先暂停执行。
♦ 第二次调用:Print(123)中,n=123,满足n>9,所以先调用函数Print(12),下面的代码先暂停执行。
♦ 第三次调用:Print(12)中,n=12,满足n>9,所以先调用函数Print(1),下面的代码先暂停执行。
Print(1)中,n=1,不满足n>9,不再调用函数,下面的代码执行,1%10=1,屏幕上会打印1,并函数调用会依次回归。
♦ 第三次调用回归:12%10=2,,屏幕上会打印2。
♦ 第二次调用回归:123%10=3,屏幕上会打印3。
♦ 第一次调用回归:1234%10=4,屏幕上会打印4.
递归函数执行结束,最终在屏幕上呈现1 2 3 4。
3. 练习(2)
● 题目:编写函数不允许创建临时变量,求字符串的长度
▼思路:我们就用Strlen函数用来实现吧,Strlen函数计算并返回字符串的长度。
Strlen("abcdef") → 1+Strlen("bcdef") → 1+1+Strlen("cdef") → 1+1+1+Strlen("def") → ......
相当于我们,每读完一个字符,就向后跳一个字符,字符指针+1,直到遇到 ' \ 0 ' 为止;
当*str=' \ 0 ' 就返回0,否则返回1 + Strlen(str + 1)的结果,下面是参考代码。
#include <stdio.h>
int Strlen(char* str)
{
if (*str != '\0')
return 1 + Strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "abcdef";
int ret = Strlen(arr);//数组名指向字符串首元素的地址
printf("%d\n", ret);
}
♦ 第一次调用:Strlen("abcdef")中,*str=' a ',不为' \ 0 ' ,所以先执行1+Strlen("bcdef"),调用函数Strlen("bcdef"),下面的代码先暂停执行。
♦ 第二次调用:Strlen("bcdef")中,*str=' b ',不为' \ 0 ' ,所以先执行1+Strlen("cdef"),调用函数Strlen("cdef"),下面的代码先暂停执行。
♦ 第三次调用:Strlen("cdef")中,*str=' c ',不为' \ 0 ',所以先执行1+Strlen("def"),调用函数Strlen("def"),下面的代码先暂停执行。
♦ 第四次调用:Strlen("def")中,*str=' d ',不为' \ 0 ',所以先执行1+Strlen("ef"),调用函数Strlen("ef"),下面的代码先暂停执行。
♦ 第五次调用:Strlen("ef")中,*str=' e ',不为' \ 0 ',所以先执行1+Strlen("f"),调用函数Strlen("f"),下面的代码先暂停执行。
♦ 第六次调用:Strlen("f")中,*str=' f ',不为' \ 0 ',所以先执行1+Strlen(" "),调用函数Strlen(" "),下面的代码先暂停执行。
第七次调用时,*str=' \ 0 ',返回0,函数调用会依返回。
♦ 第六次调用回归:1+Strlen(" "),1+0=1,返回1。
♦ 第五次调用回归:1+Strlen("f"),1+1=2,返回2。
♦ 第四次调用回归:1+Strlen("ef"),2+1=3,返回3。
♦ 第三次调用回归:1+Strlen("def"),3+1=4,返回4。
♦ 第二次调用回归:1+Strlen("cdef"),,4+1=5,返回5。
♦ 第一次调用回归:1+Strlen("bcdef"),5+1=6,返回6。
递归函数执行结束,最终递归函数返回6,屏幕上打印6。
三、递归与迭代
1. 练习(1)——两种方法
● 求n的阶乘。(不考虑溢出)
♦ 方法一:递归
▼已知求阶乘的公式,我们可以很容易的写出递归函数,我把函数名设为Fac,下面是参考代码。
#include <stdio.h>
int Fac(int n)
{
if (n <= 1)
return 1;
else
return n* Fac(n - 1);
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = Fac(num);
printf("%d\n", ret);
return 0;
}
♦ 方法二:迭代(循环)
※ 我们可以发现这样的一个问题,如果使用Fac函数求10000的阶乘,就会死递归,一直开辟栈空间,导致栈溢出,最终程序会崩掉。
▼因此这道题我们也可以用迭代来做,所谓的迭代就是指非递归。循环就是迭代的一种形式。
#include <stdio.h>
int Fac(int n)
{
int i = 0;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = Fac(num);
printf("%d\n", ret);
return 0;
}
2. 练习(2)——两种方法
● 求第n个斐波那契数。(不考虑溢出)
▼已知求斐波那契数的公式,我们可以很容易的写出递归函数,下面是参考代码。
♦ 方法一:递归
#include <stdio.h>
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
♦ 方法二:迭代
※ 我们可以发现这样的一个问题,如果使用Fib函数计算第50个斐波那契数字的时候特别消耗时间,计算第100个斐波那契数就会死递归,导致栈溢出,最终程序会崩掉。
▼所以这道题可能比起递归更适合用常规的方法来做。
▼思路:把斐波那契数列中相邻的三个数依次设为a,b,c,a+b=c,
计算的时候我们可以把b的值赋给a,c的值赋给b,再由a和b的和计算c的值,这是一个循环,进入循环的条件是n>2,因为n=3开始才会有a,b,c三个数。
最后结束循环后,第n次斐波那契数就是c的值,返回c即可。
#include <stdio.h>
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;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
四、三道例题
▼接下来我们再做三道例题来加深对递归函数理解吧!由于时间关系,我把参考代码放在这,请大家可以自行训练,一定要独立思考哦!
1. 例一
● 编写一个函数实现n的k次方,使用递归实现
#include <stdio.h>
double Pow(int n, int k)
{
if (k == 0)
return 1;
else if (k > 0)
return n * Pow(n, k - 1);
else
return 1.0/Pow(n,-k);
}
int main()
{
int num = 0;
int i = 0;
double ret = 0.0;
scanf("%d %d", &num, &i);
ret = Pow(num,i);
printf("%lf\n", ret);
return 0;
}
2. 例二
● 写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
● 例如,调用DigitSum(1729),则应该返回1 + 7 + 2 + 9,它的和是19
#include <stdio.h>
int DigitSum(int n)
{
if (n > 9)
return DigitSum(n / 10) + n % 10;
else
return n;
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = DigitSum(num);
printf("%d\n", ret);
return 0;
}
3. 例三
● 编写一个函数 reverse_string(char * string)(递归实现)
● 实现:将参数字符串中的字符反向排列,不是逆序打印。
● 要求:不能使用C函数库中的字符串操作函数。
#include <stdio.h>
int Strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
void reverse_string(char* string)
{
char tmp = *string;
int len = Strlen(string);
*string = *(string + len - 1);
*(string + len - 1) = '\0';
if(Strlen(string+1)>=2)
reverse_string(string + 1);
*(string + len - 1) = tmp;
}
int main()
{
char arr[] = "abcdef";
reverse_string(arr);
printf("%s\n", arr);
return 0;
}
※ 最后,我想说的是,许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰,但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
❤ 好啦,本期分享的内容就这些啦,感谢大家的收看!制作不易,记得三连哦~ 如有问题,请多多指正,谢谢大家!我们下期再见!