递归
递归的基本概念
求n!的函数
int Factorial(int n){
if(n==0) //递归出口
return 1;
return n*Factorial(n-1); //递归调用
}
递归函数的调用,和普通函数的调用一样是通过栈来实现的。在JAVA语言中使用递归调用,递归函数的每一次调用会在JVM中创建了一个帧,并把这个帧压入栈空间的栈顶
栈是先进先出
因为递归调用不成立后,也就意味着这一次的函数调用已调用经执行结束了,而这一次递归的函数还有下一条执行语句,而下一条执行语句调用的,恰好是递归调用不成立时的上一层。 感觉说的有点抽象,但是,一定要时刻谨记,是按语句顺序执行的,递归并不会并发执行语句
那么递归处理的顺序就是,先开始处理的问题,最后才能结束处理。
假设如下问题的依赖关系:
【A】----依赖---->【B】----依赖---->【C】
我们的终极目的是要解决问题A,
那么三个问题的处理顺序如下:
开始处理问题A;
由于A依赖B,因此开始处理问题B;
由于B依赖C,开始处理问题C;
结束处理问题C;
结束处理问题B;
结束处理问题A
所谓递归,简单点来说,就是一个函数直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
我们可以把” 递归 “比喻成 “查字典 “,当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词。
可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。(摘自知乎的一个回答)
递归与循环
理论上,任何循环都可以重写为递归形式
有时候,为栈限制,需要“尾递归”
java不支持尾递归
有些语言没有循环语句,只能使用递归
循环改递归
改为递归的关键是发现逻辑“相似性”
不要忘记递归的“出口”
for(int i=0;i<10;i++){
System.out.println(i);
}
//打印从o-n
//一个方法调用自身不可以永远调用相同的函数,要有个参数,使得每次调用条件有不同,即不要忘记递归的出口
public static void f(int n){
//先调用0-n-1项,之后最后打印n
if(n>0) f(n-1);//打印从0-n-1
System.out.println(n);//打印n
}
public static void f2(int n){//n表示打印的最后一项
//先打印第一项0,后面用我的下属执行后面的工作
//一但发现递归有问题,可能是参数出现了问题
//我打印的第一项无法表达,开始项这个第一项是固定的,只有我第一次进来的时候要打印0,但我后面进行调用进来的时候我要打印的是1,这里这个0是个固定的数字,是不合理的。所以可以调整参数
public static void f2(int begin,int end){
if(begin>end) return;
System.out.println(begin);
f2(begin+1,end)
//这里只是相似性完成了,但递归的出口并没有完成,如何用
//第一种方法是,在if() f2(begin+1,end)
//第二种方法是,在每一个方法的开始出进行检查,发现一个不合理的情况,则把他终止
//if(begin>end) return;
}
public static void main(String[] args){
f(9);
f2(9);
}
构造相似性
~如果没有明显的相似性,需要主动构造
~不能相似的原因很可能是缺少参数
~递归与数学上的递推公式很类似
//对一个数组的所有元素进行求累加和
public static int addALL(int[] a){
int x=0;
for(int i=0;i<a.length;i++)x+=a[i];
return x;
}
public static void main(String[] args){
int[] a={2,5,3,9,12,7);
int sum=addAll(a);
System.out.println(addALL(a));
}
//求a数组中,从begin到结束的元素和
public static int f(int[] a,int begin){/*因为a传进来,在执行的过程中数组的大小不会改变,再次调用了f(a)时,是重复了刚才的过程,即类似于死循环。如何让输入的参数有什么变化,这是关键,如何求和,求和是把参数一项一项的来求,但还可以偷懒,他的下属把后面的项求出来,把值赋予,之后再把所属的项加起来;二分法分别对两个定律求和,找一个中间点,我这里用首元素到下一项结尾的加起来,所以这里的参数要有开始的一项*/
//1.a[begin]+(begin+1'''结束)
//2.(a[0]'''end-1)+a[end]
//3.折半求和,mid=(begin+end)/2 [begin,mid) [mid,end)
if(begin==a.length) return 0;
int x=f(a,begin+1);//从当前任务的下一项到最后一项
return x+a[begin];
}
public static void main(String[] args){
int[] a={2,5,3,9,12,7);
int sum=f(a,0);//a从第零项开始的累加
System.out.println(sum);
}
public class 从0到9整数 {
//打印从0-n
public static void f(int n){
if(n>0) f(n-1);//打印从0-(n-1) 当n=0时,这句话就不执行了,但后面打印的执行
System.out.println(n);//我只打印最后一个n
//当n=3时,调用的f(2),当n=2时,调用的是f(1),当n=1时,调用f(0),当n=0时,
/*
* f(3)输出3
* f(2)输出2
* f(1)输出1
* f(0)输出0
*
*/
}
//从begin
public static void f2(int begin,int end){
if(begin>end) return;
System.out.println(begin);
f2(begin+1,end);
//可以在f2前面写if,但在最开始前面判断,每一个方法的开始处做一个检查,当发现不合理的情况进行终止
/*
* begin=0,end=3
* 判断,输出0,调用f2(1,3)
* 输出1,调用f2(2,3)
* 输出2,调用f2(3,3)
* 输出3,调用f2(4,3)
* 判断,则返回,不做任何终止
* 结束
* 结束
* 结束
* 结束
*/
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//f(9);
f2(0,9);
}
}