理论上,任何递归都可以表述成循环的格式,任何循环都可以重写为递归形式。
递归要层层深入的调用,可能会产生栈溢出的问题 。因为它要用到栈,栈的大小在一般的语言中都是有限制的。 有些语言支持”尾递归“,这样就可以突破栈的限制,可以不用每次都压栈。java不支持尾递归,C语言可以用goto语句模拟出尾递归的效果,但一般不主张这么做,因为会降低代码的可读性。有些语言中没有循环语句,只能使用递归,比如Lisp。
如何把循环改成递归?
主要是要发现循环过程中的”相似性“。写一个方法要想实现递归调用,就是它直接或间接地调用自身,调用和被调用者这两个应用应该具有相似性。
例:
public class A
{
public static void main(String []args)
{
for(int i=0;i<10;i++)
{
System.out.println(i);
}
}
}
打印从0到9的整数public class A
{
public static void f()
{
...
f();
}
public static void main(String []args)
{
f();
}
}
如果方法是无参的,它去调用自身,就回到了程序左括号开始执行,就会产生类似于死循环,程序永远结束不了。一个方法去调用自身不能是完全相同的环境,这样执行永远不能结束。必然会有一些变化,这个变化是由参数引起的
比如有一个参数n
//表示打印从0到n
public static void f(int n)
{
...
f();
}
多一个参数n,在调用的时候就可以调整参数使得每次调用条件有一点点不同,就可以在适当的时候终止递归,这就是不要忘记递归出口 。
比如说领导交给我们一项工作,我们原封不动的交给下属,这样是不对的
public static void f(int n)
{
f(n);
}
比如我只打印n,请另外一个人打印0~n-1
public static void f(int n)
{
f(n-1); //打印0-(n-1)
System.out.println(n); //打印n
}
如果每次调用这个递归,这个递归会无限的调用下去。
当n=3时调用2,n=2时调用1,n=1时调用0,n=0时调用-1······这样循环会一直持续下去,这种递归的调用不会有终止,这样就没有出口。
在递归中一般总是要有一个出口,这里要判断在什么条件下调用递归,什么时候不调用。如果在某一层的递归中不再继续调用,这个时候递归就可以逐层返回了。当n=0时需不需要调用?n=1需不需要调用?如果n=1显然是需要调用的,因为0还没有执行。如果n=0,就不需要调用,所以n>0时候调用f(n-1)
//打印0~9
public class A
{
public static void f(int n)
{
if(n>0) f(n-1);
System.out.println(n);
}
public static void main(String[] args)
{
f(9);
}
}
递归的关键就是要找到相似性。
public class A
{
public static void f(int n)
{
if(n>0) f(n-1);
System.out.println(n);
}
//打印begin~end
public static void f2(int begin,int end)
{
if(begin>end)
System.out.println(begin);
f2(begin+1,end);
}
public static void main(String[] args)
{
f2(0,9);
}
}
递归的设计是很灵活的,并非只有一种解法。
循环改递归的关键是发现逻辑相似性,不要忘记递归出口。
如果没有明显的相似性,需要主动构造。
不能相似的原因很可能是缺少参数。填参数是主要技巧。
另一个例子:
public class B {
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));
}
}
改写成递归形式public class B {
public static int f(int[]a,int begin)
{
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);
System.out.println(f(a));
}
}
C++循环与递归例子
#include<iostream>
using namespace std;
int addAll(int*a,int a_length)
{
int x=0;
for(int i=0;i<a_length;i++)
x+=a[i];
return x;
}
int f1(int *a,int a_length,int begin)
{
if(begin>=a_length)
return 0;
int x=f1(a,a_length,begin+1);
return x+a[begin];
}
int f2(int *a,int a_length,int end)
{
if(end<0)
return 0;
int x=f2(a,a_length,end-1);
return x+a[end];
}
int main()
{
int a[]={1,2,3,4};
int n=sizeof(a)/sizeof(a[0]);
//int m=addAll(a,n);
//int m=f1(a,n,0);
int m=f2(a,n,n-1);
cout<<m;
return 0;
}
判断两个字符串是否相同
public class A {
public static boolean isSameString(String s1,String s2)
{
return s1.equals(s2);
}
public static boolean f(String s1,String s2)
{
if(s1.length()!=s2.length())return false;
if(s1.length()==0)return true;
if(s1.charAt(0)!=s2.charAt(0))return false;
return f(s1.substring(1),s2.substring(1));
}
public static void main(String[]args)
{
System.out.println(isSameString("abc","abcd"));
System.out.println(f("abc","abcd"));
}
递归调用仅仅是被调函数恰为主调函数
注意每次调用的层次不同
注意每次分配形参并非同一个变量
注意返回的次序