递归
从程序设计的角度看,递归是一种程序设计方法。函数直接或间接地调用自身,称为递归调用。递归调用是用相同的策略去解决规模更小的问题,直至问题规模达到某个边界条件时,不再进行递归调用,而是直接处理。
函数递归调用的嵌套层数称为递归层次。其中,其他函数对递归函数的调用为第0层,递归函数第一次调用自身为第1层......以此类推。递归函数的执行过程为:
1. 执行第n-1(n>0)层的代码到调用递归函数时,进入第n层,执行第n层递归函数的代码。
2. 第n(n>0)层递归函数的执行结果返回到第n-1层,之后执行第n-1层调用递归函数之后的代码。
编写递归函数时需要注意:
1. 递归函数传入引用类型的数据时会改变数据本身。
2. 递归必须设置退出条件,而且每次递归时都是向退出条件逼近,否则就变成了无限递归,最终会出现StackOverflowError。
阶乘
阶乘的定义是:n! = 1 * 2 * 3 * ... * n。特别的有,0! = 1。
根据阶乘的定义,可以得到n! = n * (n - 1)!。也就是说,n的阶乘可以通过先求n - 1的阶乘再乘以n得到。所以可以通过递归实现求一个数的阶乘,而递归的退出条件即为n == 0时返回1。
1 public static int factorial(int n) { 2 if (n == 0) return 1; 3 return n * factorial(n - 1); 4 }
最大公因数
求a和b(a ≥ b)的最大公因数的过程为:
a = b * q1 + r1
b = r1 * q2 + r2
r1 = r2 * q3 + r3
...
rn-2 = rn-1 * qn-1 + rn
rn-1 = rn * qn
可以发现,上一个式子的除数和余数分别是当前式子的被除数和除数。直到出现一个式子的余数为0时,该式子的除数即为最大公因数。这种求最大公因数的方式称为辗转相除法。
特别的,任何一个数和0的最大公因数都是这个数本身。
可以通过递归实现辗转相除法求最大公因数,而递归的退出条件即为除数为0,此时被除数为最大公因数。
1 public static int gcd(int a, int b) { 2 if (a < b) return gcd(b, a); 3 if (b == 0) return a; 4 return gcd(b, a % b); 5 }
斐波那契数列
1, 1, 2, 3, 5, 8, 13, ...这个数列被称为斐波那契数列。斐波那契数列的特点是:从第3项开始,当前项为前两项之和。所以斐波那契数列的递推公式为:F(n) = F(n - 1) + F(n - 2)。
求斐波那契数列第n项的数值,可以转变为求其第n-1项和第n-2项的数值之和。可以通过递归来实现该功能,而退出条件即为n == 1和n == 2时返回都为1。
1 public static int fibonacci(int n) { 2 if (n == 1 || n == 2) return 1; 3 return fibonacci(n - 1) + fibonacci(n - 2); 4 }
汉诺塔问题
汉诺塔游戏(http://www.4399.com/flash/109504.htm#sim2|109504):有三根柱子,其中第一根柱子上有n个圆环,越往下的圆环越大。
游戏任务:将第一根柱子上的所有圆环移动到第三根柱子上。
游戏规则:每次移动都是移动最上面的圆环,且必须保证每次都是小环在上大环在下。
例如,移动3个圆环,效果如图所示:
移动n(n > 0)个圆环的一般步骤如下:
a. 将n - 1个圆环从第一根柱子移到第二根柱子。
b. 将第n个圆环从第一根柱子移动到第三根柱子。
c. 将n - 1个圆环从第二根柱子移动到第三根柱子。
所以,可以通过递归实现汉诺塔。而递归退出的条件则为n == 1时结束。
1 public static void hanoi(int n, char a, char b, char c) { 2 if (n == 1) { 3 // 1个圆环,直接从第一根柱子移动到第三根柱子 4 System.out.println("第" + n + "个圆环从第" + a + "根柱子移动到第" + c + "根柱子。"); 5 } else { 6 // 将n - 1个圆环从第一根柱子移动到第二根柱子上 7 hanoi(n - 1, a, c, b); 8 // 将第n个圆环从第一根柱子移动到第三根柱子上 9 System.out.println("第" + n + "个圆环从第" + a + "根柱子移动到第" + c + "根柱子。"); 10 // 将n - 1个圆环从第二根柱子移动到第三根柱子上 11 hanoi(n - 1, b, a, c); 12 } 13 }
测试3个圆环的输出结果:
八皇后问题
八皇后游戏(http://www.7k7k.com/swf/49842.htm):有一个8*8的棋盘和8个皇后棋子
游戏任务:在棋盘上摆放8个皇后。
游戏规则:摆放的8个皇后不能相互攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
摆放第n个皇后的一般步骤如下:
a. 摆放在第n行的位置上。
b. 判断是否可以摆放在这个位置上,即摆放后是否依然满足皇后不会相互攻击。
c. 如果第n行上没有一个位置使得n个皇后不会相互攻击,证明第n - 1个皇后摆放的位置不正确,需要改变位置。
八皇后问题可以通过递归实现,递归的退出条件为成功摆放最后一个皇后。
1 public static boolean check(int[] bound, int x) { 2 for (int i = 0; i < x; i++) { 3 // bound[i] == bound[x]表示两个皇后在同一列,x - i == |bound[x] - bound(i)|表示两个皇后在同一斜线 4 if (bound[i] == bound[x] || x - i == Math.abs(bound[x] - bound[i])) return false; 5 } 6 return true; 7 }
1 public static int put(int[] bound, int n) { 2 int count = 0; 3 for (int i = 0; i < 8; i++) { 4 bound[n] = i; 5 if (check(bound, n)) { 6 // n == 7表示已经成功摆上最后一个皇后,该条件作为递归的退出条件 7 if (n == 7) { 8 count++; 9 for (int index : bound) 10 System.out.print((index + 1) + " "); 11 System.out.println(); 12 } else { 13 count += put(bound, n + 1); 14 } 15 } 16 } 17 return count; 18 }
其中,check()方法用于判断摆放皇后的位置是否正确;put()方法用于计算8皇后摆放方案。
通过测试,得到结果如下:
可以看到,八皇后一共有92种摆法。以“5 1 4 6 8 2 7 3”为例,“5”表示第一个皇后摆放在第1行的第5个位置,“6”表示第四个皇后摆放在第4行的第6个位置......