007——递归(树的前置知识点)
目录
创建副本
递归
直接调用
间接调用
递归的具体流程又是什么样子的?
递归函数的组成:
递归可以用来解决什么问题?
例子1:求和问题
例子2:斐波那契数列
补充:
说到递归,我们可以简单理解成某个函数直接或间接的调用自身。递,意思是递进;归,意思是回归。而函数调用的本质是创建副本。我们用下面的图解来说明函数的调用为什么是创建副本。
创建副本
在上图的主函数中,调用了fun函数,那么程序在执行到fun(6);语句的时候会创建副本,并执行fun副本中的内容,在执行完fun副本的代码后,便会返回main方法,继续执行下面的代码。
继续说到递归,是某个函数直接或间接的调用自身
递归
直接调用
直接调用的例子如下:
#include<stdio.h>
#include<stdlib.h>
void fun(int a) {fun(a+10);//直接调用
}
int main() {fun(4);
}
间接调用
间接调用的例子如下:
#include<stdio.h>
#include<stdlib.h>
void elfun(int b) {fun(b + 5);//间接调用
}
void fun(int a) {elfun(a+10);
}
int main() {fun(4);
}
递归的具体流程又是什么样子的?
我们那下面这个有限递归的代码举例
#include<stdio.h>
#include<stdlib.h>
int x = 0;
void fun(int a) {printf("%d\n", a);x++;while(x<3)fun(a+10);printf("***********\n");
}
int main() {fun(4);
}
运行结果:
该代码的运行流程图是这样的
程序从main函数开始运行,当运行到fun函数时,自动创建副本,代码执行立刻从main函数跳转到副本1,执行副本1函数,执行副本1函数的时候又碰见调用函数fun,接着创建副本2函数并跳转,直到不能跳转为止,在上述代码中程序一共创建了3个副本,当最后一个副本也就是副本3执行完以后,程序运行跳转到上一个副本2,继续执行副本2没有执行完的代码,如此直到第一个副本执行完毕,程序继续执行main函数下面没有执行完的代码。
递归函数的组成:
①递归出口/终止条件/边界条件:停止递归调用的条件,避免死循环/爆内存
②递归体
递归可以用来解决什么问题?
如果一个问题可以分成若干个小问题,并且这些小问题的解决思路和大问题一样
例子1:求和问题
(注意,我们在写递归函数的时候,不要想太多后面它是怎么调用的,要不然有时候容易绕进去,我们只需要搞清楚递归函数的功能(可以看看一次递归函数调用的结果是什么来判断功能是否正确)即可,不要深想)
代码:
#include<stdio.h>
#include<stdlib.h>
int add(int a) {if (a == 1) {return 1;}int ans;ans = a + add(a - 1);return ans;
}
int main() {int n;scanf_s("%d", &n);int sum = 0;sum = add(n);printf("1到%d的整数和为%d", n, sum);
}
运行结果:
流程图
例子2:斐波那契数列
#include<stdio.h>
#include<stdlib.h>
int add(int a) {if (a == 1||a==2) {return 1;}int ans;ans = add(a - 2) + add(a - 1);return ans;
}
int main() {int n;scanf_s("%d", &n);int sum = 0;sum = add(n);printf("第%d项的斐波那契数列为%d", n, sum);
}
补充:
1.递归只是书写代码的一种格式/方式
2.任何递归代码都可以转化成非递归形式,大部分情况下要借用栈来实现,而一些简单的代码,可以通过循环或者其他形式来实现
3.工程开发中不要用递归,因为递归出口很容易出现问题
4.尾递归,是一种编译层面的优化,是当递归语句在结尾的时候,直接创建一个副本不断覆盖,而不会占用其他空间