什么是递归
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
递归的两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
代码实例讲解
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4.
#include <stdio.h>
//接受一个整型值(无符号),按照顺序打印它的每一位。
//例如
//输入:1234,输出 1 2 3 4.
void Print(unsigned int x)
{
if (x > 10)
{
Print(x/10);
}
printf("%d ", x%10);
}
int main()
{
unsigned int n = 0;
scanf("%d", &n);
Print(n);
return 0;
}
递归 = 递推 + 回归
红色的是递推,蓝色的是回归
编写函数不允许创建临时变量,求字符串的长度。
下面是非递归的写法,用于实现字符串长度的求解,但值得注意的是,我们定义的函数的内部创建了一个名为count
的临时变量,这显然不符合我们的要求。
#include <stdio.h>
#include <string.h>
//编写函数不允许创建临时变量,求字符串的长度。
int mystrlen(char arr[])
{
int count = 0;
while (*arr != '\0')
{
arr++;
count++;
}
return count;
}
int main()
{
char arr[] = "abcde";
//printf("%zd\n", strlen(arr));
int len = mystrlen(arr);
printf("%d\n", len);
return 0;
}
下面是用递归的写法,并且用图来介绍
int mystrlen(char arr[])
{
if (*arr == 0)
return 0;
else return 1 + mystrlen(arr + 1);
}
int main()
{
char arr[] = "abc";
//printf("%zd\n", strlen(arr));
int len = mystrlen(arr);
printf("%d\n", len);
return 0;
}
递归与迭代
我们先来看两个例子
求n的阶乘。(不考虑溢出)
//fac(n) = 1 , n=1;
//fac(n) = n*fac(n-1) , n>=2.
int factorial(int n)
{
//fac(n) = 1 ,n=1;
//fac(n) = n*fac(n-1),n>=2.
if (n < 2)
{
return 1;
}
else
{
return n * factorial(n-1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d\n", ret);
return 0;
}
求第n个斐波那契数。(不考虑溢出)
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,其数值为:1、1、2、3、5、8、13、21、34……在数学上,这一数列以如下递推的方法定义:F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。
int Fib(int n)
{
if (n <= 2)
{
return 1;
}
else
{
return Fib(n - 1) + Fib(n - 2);
}
}
int main()
{
int i = 0;
scanf("%d", &i);
int ret = Fib(i);
printf("%d\n", ret);
return 0;
}
从上述两个例子我们可以发现,用递归来写就好像直接用数学表达式写,这样确实简单,而且代码量也少,逻辑也显而易见,但是有一个问题就是,当我们计算较大数的阶乘的时候,你就会发现不行了。这就不得不提到一个问题——栈溢出(Stack overflow).
当我们调用函数的时候,都会在栈上为函数分配空间,递归的层次如果较深的话,即你指定的数字迟迟无法达到限制条件的时候,会一直递归,一直调用函数,而栈是有空间限制的,就像你倒一杯水,倒满了就无法再往进到了,此时程序就无法再执行了,这种情况就称为栈溢出。
此时我们就不能再用递归来求解问题,需要用迭代来解决。即运用非递归来写。
求n的阶乘。
int factorial(int n)
{
int i = 0;
int sum = 1;
for (i = n;i > 0;i--)
{
sum *= i;
}
return sum;
}
求第n个斐波那契数。
int Fib(int n)
{
//a + b = c
int a = 1;
int b = 1;
int c = 1;
while(n>=3)
{
c = a + b;
a = b;
b = c;
n--;//
}
return c;
}
因为是从前往后算,当n=3时,只循环一次;当n=4的时候,就得循环2次,第一次算出前一个斐波那契数3,然后第二次循环才算出此次斐波那契数4。所以循环的调整条件为n--
。
这样的迭代代码运算效率要比递归高不少。
关于什么时候运用迭代或者运用递归可以记住以下两条建议
- 如果使用递归很容易想到,写出来的代码没有明显的缺陷,就可以使用递归。
- 但是如果写出的代码有明显的问题,如:栈溢出、效率低下等,就需要用迭代的方法。
还有两个运用递归的代码,可以练习一下
- 汉诺塔问题
- 青蛙跳台阶问题
后续会有关于这两个代码的讲解,敬请期待!!