递归函数
递归
递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。
当函数在一直递推,直到遇到墙后返回,这个墙就是结束条件。
所以递归要有两个要素,基线条件与递推关系
注:
递归的时候,每次调用一个函数,计算机都会为这个函数分配新的空间,这就是说,当被调函数返回的时候,调用函数中的变量依然会保持原先的值,否则也不可能实现反向输出。
例题
# 计算5的阶乘
#include <stdio.h>
int factorial(int n)
{
int result;
if (n<0) //判断例外
{
printf("输入错误!\n");
return 0;
}
else if (n==0 || n==1)
{
result = 1; //回推墙
}
else
{
result = factorial(n-1) * n; //递推关系,这个数与上一个数之间的关系。
}
return result;
}
int main(){
int n = 5; //输入数字5,计算5的阶乘
printf("%d的阶乘=%d",n,factorial(n));
return 0;
}
程序在计算5的阶乘的时候,先执行递推,当n=1或者n=0的时候返回1,再回推将计算并返回。由此可以看出递归函数必须有基线条件。
练习
# 采用递归函数实现求和
def Sum(lst):
# 一个空 list 本身等同于 False
if not lst:
return 0
else:
# pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
return lst.pop() + Sum(lst)
# 输出求和
print(Sum([1, 2, 3, 4, 5, 6, 7, 8, 9]))
# 求列表包含的元素数
# 基线条件:当列表为空时,返回值为0。
# 递归条件:每递归一次,得到的数字加1,并且要使列表中元素减1。
def LenList(list):
if list == []:
return 0
else:
return 1 + LenList(list[1:])
# 输出列表中元素的个数
print(LenList([1, 2, 3, 4, 5, 6, 7, 8, 9]))
# 找出列表中最大的数字
def FindMax(my_list):
if len(my_list) == 0:
return 0
return max(my_list.pop(), FindMax(my_list))
# 输出列表中最大的数字
print(FindMax([1, 2, 3, 4, 5, 6, 7, 8, 9]))
特点
递归函数特点:
1. 每一级函数调用时都有自己的变量,但是函数代码并不会得到复制,如计算5的阶乘时每递推一次变量都不同;
2. 每次调用都会有一次返回,如计算5的阶乘时每递推一次都返回进行下一次;
3. 递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;
4. 递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;
5. 递归函数中必须有基线条件,即终止语句。
一句话总结递归:自我调用且有完成状态。
效率
1.系统栈(也叫核心栈、内核栈)
是内存中属于操作系统空间的一块区域,其主要用途为: (1)保存中断现场,对于嵌套中断,被中断程序的现场信息依次压入系统栈,中断返回时逆序弹出; (2)保存操作系统子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量。
2.用户栈
是用户进程空间中的一块区域,用于保存用户进程的子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量。
我们编写的递归程序属于用户程序,因此使用的是用户栈。
3.栈溢出
函数调用的参数是通过栈空间来传递的,在调用过程中会占用线程的栈资源。而递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。
综上:
函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个调用栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N次局部变量、N次形参、N次调用函数地址、N次返回值,势必效率低.
优点
代码简洁、清晰,易懂
循环能干的事,递归都能干;递归能干的循环不一定能干
对于我们,能用循环解决的,尽量不使用递归.
转载至FlyWine,模仿大佬学习的过程。