递归是什么?
递归就是程序自身地调用自己,也是一种算法,递归式将一个大型复杂发问题转化为类似的小型问题。而且用的代码量较少,主要思考方式在于把大事化小。 递归的应用场景很多,在有子类的情况下都可以用递归。以后的二叉树,红黑树,也都会用到递归。
递归的条件
递归有两个必要条件。 第一个必要条件:存在限制条件,当满足这个限制条件的时候,递归便不再继续。 第二个必要条件:每次递归之后,都会越来越解决这个限制条件。
递归实例练习
在使用递归的时候,如果没思路的话,可以使用编译器的调试功能或者画图来解决。
使用递归打印应该无符号数的每一位,比如 1234 ,输出 1 2 3 4 。
void print(int a)
{
if (a > 9)
{
print(a / 10);
}
printf("%d ", a % 10);
}
int main()
{
int a = 0;
scanf("%d", &a);
print(a);
return 0;
}
因为递归是函数自己调用自己,所以写的时候就要把函数写在程序当中,因为这道题是输出每一位,所以就是先输出 1 然后 2 然后 3 然后 4 。画图来看: 上面的图片就是整个递归的所有过程,因为递归就是递下去,归回来。所以我们只要确定好限制条件和判断条件就可以这样画图来完成。
练习二:不创建临时变量,求字符串长度。
int my_strlen(char* s)
{
if (*s != '\0')
{
return 1 + my_strlen(s + 1);
}
else
{
return 0;
}
}
int main()
{
char arr[] = "abc";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
这道题的主要判断点就是有一个字符不为0,就+1,所以用字符是否为 ‘\0’ 来判断递归是否结束就可以了。所以每次递下去返回的时候就 +1 ,这样就完成了对字符串长度的统计。不过要注意的是,数组名就是首元素地址,所以用指针来接收就能一步一步指向下一个字符。指针 +1 就是指向下一个下标。当然不用指针接收也可以,写成 char s[] 也可以的。
练习三:求 n 的阶乘
int factorial(n)
{
if (n <= 1)
{
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;
}
因为求阶乘的话,最小值是1,如果是 0 的话,最后的结果就是 0 了,所以设置条件,当 n<=1 的时候,就返回 1 。当 n 大于 1 的时候,就递归下去,每次为 n * (n-1) 。
练习四:经典递归问题之斐波那契数列
我们要知道的是,斐波那契数列数列的第 n 项等于前两项的和。第一第二项等于 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 - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fib(n);
printf("%d\n", ret);
return 0;
}
这里就发现了每次递归的时候,都会产生很多数据,就像计算第50个斐波那契数列就会产生49 48 然后继续递归产生数据 。所以会重复计算,浪费大量时间。所以我们就可以考虑使用循环来算。代码如下:
int Fib(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 fib = 0;
scanf("%d", &fib);
int ret = Fib(fib);
printf("%d\n", ret);
return 0;
}
这里通过循环来算的话,效率就会很高。因为省去了递归那样的重复计算的时间。所以就可以知道什么时候用递归。
什么时候用递归
1 当解决一个问题递归和非递归都可以使用,且递归没有明显问题,那就可以使用递归。 2 当解决一个问题递归写起来很简单,非递归比较复杂,且递归没有明显问题,那就可以使用递归。 3 如果说用递归解决问题,写起来简单,但是有明显问题,那就不能使用递归。 递归不能解决问题的话,非递归再难也要写。
|