转自:http://blog.csdn.net/speedme/article/details/21654357
递归真的非常非常重要!!!
我们直接从例子开始吧!
写个函数实现 N! = N × (N-1) × (N-2) × ... × 2 × 1
[java] view plain copy print ? public static int factorial(int N) { if (N == 1) return 1; return N * factorial(N-1); } 上面的程序虽然简单,但我们要了解他运行的步骤,以factorial(4)为例。
[html] view plain copy print ? factorial(4) = 4 * factorial(3) = 4 * (3 * factorial(2) ) = 4 * (3 * (2 * factorial(1) ) ) = 4 * (3 * (2 * (1 * factorial(0) ) ) ) = 4 * (3 * (2 * (1 * 1) ) ) = 4 * (3 * (2 * 1) ) = 4 * (3 * 2) = 4 * 6 = 24 也可以表示为
[html] view plain copy print ? factorial(5) factorial(4) factorial(3) factorial(2) factorial(1) return 1 return 2*1 = 2 return 3*2 = 6 return 4*6 = 24 return 5*24 = 120
求p和q的最大公约数
首先我们复习一下欧几里得算法
定理:gcd(a,b) = gcd(b,a mod b) 证明:a可以表示成a = kb + r,则r = a mod b 假设d是a,b的一个公约数,则有 d|a, d|b,而r = a - kb,因此d|r 因此d是(b,a mod b)的公约数 假设d 是(b,a mod b)的公约数,则 d | b , d |r ,但是a = kb +r 因此d也是(a,b)的公约数 因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证。
下面的程序用了递归和迭代两种方法。(后面会讲到那种类型的递归可以改写成迭代)
[java] view plain copy print ? public class Euclid { // recursive implementation public static int gcd(int p, int q) { if (q == 0) return p; else return gcd(q, p % q); } // non-recursive implementation public static int gcd2(int p, int q) { while (q != 0) { int temp = q; q = p % q; p = temp; } return p; } public static void main(String[] args) { int p = Integer.parseInt(args[0]); int q = Integer.parseInt(args[1]); int d = gcd(p, q); int d2 = gcd2(p, q); System.out.println("gcd(" + p + ", " + q + ") = " + d); System.out.println("gcd(" + p + ", " + q + ") = " + d2); } } 程序执行的顺序如下
Computing the recurrence relation for x = 27 and y = 9: gcd(27, 9) = gcd(9, 27% 9) = gcd(9, 0) = 9 Computing the recurrence relation for x = 259 and y = 111: gcd(259, 111) = gcd(111, 259% 111) = gcd(111, 37) = gcd(111, 111% 37) = gcd(37, 0) = 37
递归是如何帮助我们以正确的顺序打印这些字符呢?下面是这个函数的工作流程。
1. 将参数值除以10 2. 如果quotient的值为非零,调用binary-to-ascii打印quotient当前值的各位数字 3. 接着,打印步骤1中除法运算的余数
注意在第2个步骤中,我们需要打印的是quotient当前值的各位数字。我们所面临的问题和最初的问题完全相同,只是变量quotient的 值变小了。我们用刚刚编写的函数(把整数转换为各个数字字符并打印出来)来解决这个问题。由于quotient的值越来越小,所以递归最终会终止。 一旦你理解了递归,阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能正确的完成任务。 但是,为了理解递归的工作原理,你需要追踪递归调用的执行过程,所以让我们来进行这项工作。追踪一个递归函数的执行过程的关键是理解函数中所声 明的变量是如何存储的。当函数被调用时,它的变量的空间是创建于运行时堆栈上的。以前调用的函数的变量扔保留在堆栈上,但他们被新函数的变量所掩盖,因此 是不能被访问的。 当递归函数调用自身时,情况于是如此。每进行一次新的调用,都将创建一批变量,他们将掩盖递归函数前一次调用所创建的变量。当我追踪一个递归函数的执行过程时,必须把分数不同次调用的变量区分开来,以避免混淆。 程序中的函数有两个变量:参数value和局部变量quotient。下面的一些图显示了堆栈的状态,当前可以访问的变量位于栈顶。所有其他调用的变量饰以灰色的阴影,表示他们不能被当前正在执行的函数访问。 假定我们以4267这个值调用递归函数。当函数刚开始执行时,堆栈的内容如下图所示: 执行除法之后,堆栈的内容如下: 接着,if语句判断出quotient的值非零,所以对该函数执行递归调用。当这个函数第二次被调用之初,堆栈的内容如下: 堆栈上创建了一批新的变量,隐藏了前面的那批变量,除非当前这次递归调用返回,否则他们是不能被访问的。再次执行除法运算之后,堆栈的内容如下: quotient的值现在为42,仍然非零,所以需要继续执行递归调用,并再创建一批变量。在执行完这次调用的出发运算之后,堆栈的内容如下: 此时,quotient的值还是非零,仍然需要执行递归调用。在执行除法运算之后,堆栈的内容如下: 不算递归调用语句本身,到目前为止所执行的语句只是除法运算以及对quotient的值进行测试。由于递归调用这些语句重复执行,所以它的效果 类似循环:当quotient的值非零时,把它的值作为初始值重新开始循环。但是,递归调用将会保存一些信息(这点与循环不同),也就好是保存在堆栈中的 变量值。这些信息很快就会变得非常重要。 现在quotient的值变成了零,递归函数便不再调用自身,而是开始打印输出。然后函数返回,并开始销毁堆栈上的变量值。 每次调用putchar得到变量value的最后一个数字,方法是对value进行模10取余运算,其结果是一个0到9之间的整数。把它与字符常量‘0’相加,其结果便是对应于这个数字的ASCII字符,然后把这个字符打印出来。 输出4: 接着函数返回,它的变量从堆栈中销毁。接着,递归函数的前一次调用重新继续执行,她所使用的是自己的变量,他们现在位于堆栈的顶部。因为它的value值是42,所以调用putchar后打印出来的数字是2。 输出42: 接着递归函数的这次调用也返回,它的变量也被销毁,此时位于堆栈顶部的是递归函数再前一次调用的变量。递归调用从这个位置继续执行,这次打印的数字是6。在这次调用返回之前,堆栈的内容如下: 输出426: 现在我们已经展开了整个递归过程,并回到该函数最初的调用。这次调用打印出数字7,也就是它的value参数除10的余数。 输出4267: 然后,这个递归函数就彻底返回到其他函数调用它的地点。 如果你把打印出来的字符一个接一个排在一起,出现在打印机或屏幕上,你将看到正确的值:4267
留一个程序给大家去研究研究,看看程序运行的结果。
[java] view plain copy print ? public class Region{ public static void main(String args[]){ int[] a = {1,2,3,4} ; System.out.println("final "+region(a,0,0)) ; } public static int region(int[] a,int currentSum,int i){ currentSum+=a[i]; System.out.println("out "+ currentSum) ; //按顺序输出:递归式前面 if(i<3){ region(a,currentSum,i+1) ; System.out.println("in "+ currentSum) ; //先进后出:递归式后面 } System.out.println("hello ") ; return currentSum ; } } 结果
[html] view plain copy print ? out 1 out 3 out 6 out 10 hello in 6 hello in 3 hello in 1 hello final 1 这个例子我希望大家务必去研究研究,递归的先进后出的思想体现的淋漓精致,虽然是没有意义的程序。 由上面的例子我们可以知道:递归式前面的是按顺序执行,递归是后面的则是先进后出的执行。如上面程序中,有标明 还给大家一道题:《程序员面试100题 In Java》04.二元树中和为某一值的所有路径
如果你想更深入的理解递归可以看:Recursion-wiki
Reference:
http://introcs.cs.princeton.edu/Java/23recursion/
http://en.wikipedia.org/wiki/Recursion_(computer_science)
http://blog.csdn.NET/fightforyourdream/article/details/8671276
http://beckshanling.iteye.com/blog/378483