递归
一、递归的基本概念
一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。在程序设计中,函数直接或间接调用自己,就被称为递归调用。
通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。递归函数或过程通常带有一些局部变量(如本例中的n),只有当整个函数体或过程体执行完毕时,这些局部变量才失去意义。每递归调用一次,就必须生成一组‘新’的局部变量,虽然这些新的局部变量与原来的局部变量分别具有相同的名字,但其分配的存储空间不同,其值也完全无关。
从下图可知,递归过程的执行总是一个过程体未执行完, 就带着本次执行的结果又进入另一轮过程体的执行,……,如此反复,不断深入,直到某次过程的执行遇到终止递归的边界条件时,则不再深入,而执行本次的过程体余下的部分,然后又返回到上一次调用的过程体中,执行其余下的部分,……,如此反复,直到回到起始位置上,才最终结束整个递归过程的执行,得到相应的执行结果。
#include<bits/stdc++.h> using namespace std; void p(int n) { if(n>0) { p(n-1); for (int i=1;i<=n;i++) { cout<<n; } cout<<endl; } } int main() { p(5); }
运行结果:
1
22
333
4444
55555
二、递归在计算机中的实现
计算机执行递归算法时,是通过栈来实现的。在递归过程(或函数)开始运行时,系统首先为递归建立一个栈,在每次执行递归调用语句之前,自动把本算法中所使用的值参和局部变量的当前值以及调用后的返回地址压栈(称为“保存现场”,以便需要时“恢复现场”返回到某一状态),在每次递归调用结束后,又自动把栈顶元素的值分别赋给相应的值参和局部变量(出栈),以便使它们恢复到调用前的值,接着无条件转向由返回地址所指定的位置继续执行算法。
三、如何设计递归算法
一个问题要用递归方法来解决必须符合两个条件:
1、可以把一个问题转化成一个新的问题,而新问题的解法和原问题的解法完全相同,只是处理对象的规模不同。
2、必顺要有一个明确的递归结束条件。
四、优缺点
优点:实现简单,可读性好
缺点:递归调用占用空间大,递归太深可能发生栈溢出,可能存在重复计算。
六、递归三要素
第一要素:明确你这个函数想要干什么
第二要素:寻找递归结束条件
第三要素:找出函数的等价关系式
七、例题
用递归算法求xn
分析
Xn=X*Xn-1
X0=1
#include<iostream> using namespace std; int tot; int f(int a,int n) { tot++; if (n==0) return 1; else return a*f(a,n-1); } int main() { cout<<f(2,10)<<endl; cout<<tot<<endl; }
用递归算法求x!
分析
X!=X*(X-1)!
0!=1
#include<iostream> using namespace std; int tot; int f(int n) { if (n==0) return 1; else return n*f(n-1); } int main() { cout<<f(10)<<endl; }
用递归算法求m和n的最大公约数
分析(辗转相除法)
1.求m除以n的余数
2.如果余数不为0,则让m=n,n=余数,重复步骤1
3.如果余数为0,则终止调用子程序
4.输出此时的n值。
#include<bits/stdc++.h> using namespace std; int gcd(int a,int b) { if (b==0) return a; else return gcd(b,a%b); } int main() { int x,y; cin>>x>>y; cout<<gcd(x,y); return 0; }
练习
用递归求1+2+….+n
设有n个数已经按从大到小的顺序排列,现输入x,判断它是否在这n个数中?用递归算法完成二分查找
Hanoi汉诺塔问题
用递归方法求菲波那契数列
集合的划分
数的计数