一、算法说明
递归来源于数学中的归纳法,归纳法是数学里的一种证明方法,它通常有以下两个步骤:
- 证明当 n 等于某一个值(通常是限定范围内的第一个值)时命题成立;
- 假设当 n = m 时命题成立,证明当 n = m + 1 时命题也成立。
数学中的归纳法由已知推向未知由前推向后,而在编程中通常是给定 n 的值(如 n = m)求结果,这与数学中的归纳法在过程上有些不同,多了一个回推的过程,通常是先由后向前,再由前向后(并不都是这样)。假定 n >= 1,并且 n = 1 时的结果已知,步骤如下:
- 要想求得 n = m 时的结果,必须知道 n = m - 1 时的结果,要想知道 n = m - 1 时的结果,必须知道 n = m - 2 时的结果,如此反复直到 n = 1(此时结果已知),此过程即由后向前;
- 已知 n = 1 的结果就可得到 n = 2 时的结果,得到 n = 2 时的结果就可得到 n = 3 时的结果,如此反复直到得出 n = m 的结果,此过程即由前向后。
二、递归应用举例
1.利用递归求阶乘
阶乘比较符合递归算法过程也比较好理解,所以我们先用递归来举例:
public class Main {
// 以下仅作演示用,采用整型且未对数值范围进行限定与校验,特此提醒
public static void main(String[] args)
{
int m = 10;
System.out.printf("%d! = %d%n", m, factorial(m));
}
// 求 m 的阶乘
private static int factorial(int m)
{
if (1 == m) {
return 1; // 调用终止条件,m = 1 时返回 1
}
return m * factorial(m - 1); // 调用自己,m 时的结果依赖于(m-1)时的结果
}
}
2.利用递归求斐波那契(Fibonacci)数列
斐波那契数列指的是1、1、2、3、5、8……这样的数列,用公式表示就是:
F(n) = F(n-1) + F(n-2)
代码如下:
public class Main {
// 以下仅作演示用,采用整型且未对数值范围进行限定与校验,特此提醒
public static void main(String[] args)
{
int m = 11;
System.out.printf("斐波那契数列第%d个数:%d%n", m, fibonacci(m));
}
// 求斐波那契数列第 n 个数的值
private static int fibonacci(int n)
{
if (n < 3) {
return 1; // 递归终止条件
}
return fibonacci(n - 1) + fibonacci(n - 2); // 第n项为第(n-1)项与第(n-2)项之和
}
}
以上两例仅作递归演示,实际上并不适合使用递归,因为其需要回推使得效率大大降低,对于这种明显可以使用循环解决而递归又需要回推导致效率低下的情况更适合使用循环:
1. 使用循环求阶乘
public class Main {
// 以下仅作演示用,采用整型且未对数值范围进行限定与校验,特此提醒
public static void main(String[] args)
{
int m = 10;
System.out.printf("%d! = %d%n", m, factorial(m));
}
// 求 m 的阶乘
private static int factorial(int m)
{
int product = 1;
for (int i = 2; i <= m; i++) {
product *= i;
}
return product;
}
2.使用循环求斐波那契数列
public class Main {
// 以下仅作演示用,采用整型且未对数值范围进行限定与校验,特此提醒
public static void main(String[] args)
{
int m = 10;
System.out.printf("斐波那契数列第%d个数:%d%n", m, fibonacci(m));
}
// 求斐波那契数列第 n 个数的值
private static int fibonacci(int n)
{
int a = 1; // 第1个斐波那契数
int b = 1; // 第2个斐波那契数
for (int i = 3; i <= n; i++) {
b += a; // 第i个斐波那契数
a = b - a; // 第(i-1)个斐波那契类
}
return b;
}
}
三、递归经典用法
1.快速排序
快速排序法采用的是“分而治之”的思想,比较适合使用递归而且效率也相当不错。相对前面的两个例子,它不需要回推,节省了大量时间:
public static void quickSort(int[] arr, int left, int right)
{
if (left < right) {
int mark = arr[left];
int i = left;
int j = right;
while (i < j) {
// 从后向前查找
while (j > i && arr[j] >= mark) {
j--;
}
if (j > i) {
arr[i++] = arr[j];
}
// 从前向后查找
while (i < j && arr[i] < mark) {
i++;
}
if (i < j) {
arr[j--] = arr[i];
}
}
arr[i] = mark;
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
}
2.汉诺塔(Hanoi)
汉诺塔是一个经典的算法题,常被用作递归算法案例,详细描述可见链接。代码如下:
public class Main {
public static void main(String[] args)
{
int num = 3;
System.out.printf("%d个盘移动步骤:%n", num);
hanoi(num, 'A', 'B', 'C');
}
// 将 num 个盘从 a 塔移到 c 塔
private static void hanoi(int num, char a, char b, char c)
{
if (1 == num) {
move(a, c);
} else {
hanoi(num - 1, a, c, b);
move(a, c);
hanoi(num - 1, b, a, c);
}
}
// 从 x 移到 y 上面
private static void move(char x, char y)
{
System.out.printf("%c -> %c%n", x, y);
}
}
作一个简单的总结,对于阶乘和斐波那契数列这样的问题实际上并不适合使用递归,首先它需要回推会导致计算量大大增加,其次这种问题使用循环本身很容易解决,而且代码也并不复杂。而对于快速排序这种“分而治之”的情况,并不存在回推的过程使用递归可以简化代码增强代码的可读性,对于汉诺塔使用递规不仅大大简化了代码也更易理解。总而言之,不管使用什么方法,其目的无非就是提高程序运行效率,减化代码增强代码的可读性。