递归算法实例解析
程序调用自身的编程技巧称为递归( recursion)。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
递归的三个条件:
1.边界条件
2.递归前进段
3.递归返回段
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
下面从一个最简单的问题入手,看看递归算法的实例
递归算法求解(1):1+2+3+…..+n
问题描述:求N个正整数的和。 其中 N<=2000000
package com.bean.algorithmbasic;
public class SumOfNumbersByPermutation {
/*
* 题目描述:求1+2+3+4+5+......+N。
*
* 这个经典的问题可以采用递归求解。
* 但是递归调用时,可能会报异常
* Exception in thread "main" java.lang.StackOverflowError
*
* */
static int count(int n) {
if (n > 0) {
return n + count(n - 1);
} else {
return 0;
}
}
public static void main(String args[]) {
int T=18542;
int sum = count(T);
System.out.println(sum);
}
}
递归算法求解(2):兔子繁殖问题
题目描述:古典问题:3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
分析:首先我们要明白题目的意思指的是每个月的兔子总对数;假设将兔子分为小中大三种,兔子从出生后三个月后每个月就会生出一对兔子,那么我们假定第一个月的兔子为小兔子,第二个月为中兔子,第三个月之后就为大兔子,那么第一个月分别有1、0、0,第二个月分别为0、1、0,第三个月分别为1、0、1,第四个月分别为,1、1、1,第五个月分别为2、1、2,第六个月分别为3、2、3,第七个月分别为5、3、5……
兔子总数分别为:1、1、2、3、5、8、13……
于是得出了一个规律,从第三个月起,后面的兔子总数都等于前面两个月的兔子总数之和,即为斐波那契数列。
package com.bean.algorithmbasic;
public class RabbitProblem {
/*
* 题目:古典问题:3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
* 分析:首先我们要明白题目的意思指的是每个月的兔子总对数;假设将兔子分为小中大三种,兔子从出生后三个月后每个月就会生出一对兔子,
* 那么我们假定第一个月的兔子为小兔子,第二个月为中兔子,第三个月之后就为大兔子,那么第一个月分别有1、0、0,第二个月分别为0、1、0,
* 第三个月分别为1、0、1,第四个月分别为,1、1、1,第五个月分别为2、1、2,第六个月分别为3、2、3,第七个月分别为5、3、5……
* 兔子总数分别为:1、1、2、3、5、8、13……
* 于是得出了一个规律,从第三个月起,后面的兔子总数都等于前面两个月的兔子总数之和,即为斐波那契数列。
*
* */
/*
* 递归算法设计
* */
public static int func(int x) {
if(x==1 || x==2) {
return 1;
}else {
return func(x-1)+func(x-2);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int i=1;
for(;i<=20;i++) {
System.out.println("第"+i+"个月后,兔子的数量为 "+func(i)+" 只。");
}
}
}
递归算法求解(3):猴子吃桃子
问题描述:猴子吃桃子 有一只猴子第一天摘了很多桃,觉得很高兴就立刻吃了桃总数的一半,然后觉得没吃饱又吃了3个。猴子感觉这样吃桃会立刻没有,于是就定下一个规矩:
每次在奇数天吃剩余桃总数的一半再多加3个,每次在偶数天吃剩余桃的总数的一半再多吃一个。
请输入一个天数,使得该天数的剩余桃数正好为1,请打印出猴子第一天共摘了多少个桃?
问题分解:设输入的天数为n; 首先,找到问题入口:
第1天时, 求剩余桃数:F(1) ; 其次,找到问题的递归方程,
在本题中,递归方程有两个:
若n天为奇数天:有F(n) = (F(n+1)+3)*2
若n天为偶数天:有F(n) = (F(n+1)+1)*2
其中,奇数天与偶数天相互递归。
最后,问题结束:当程序求到第n天时,F(n) = 1 已知。
package com.bean.algorithmbasic;
import java.util.Scanner;
public class MonkeysEatPeaches {
/*
* 问题描述:猴子吃桃子 有一只猴子第一天摘了很多桃,觉得很高兴就立刻吃了桃总数的一半,然后觉得没吃饱又吃了3个。
* 猴子感觉这样吃桃会立刻没有,于是就定下一个规矩:
* 每次在奇数天吃剩余桃总数的一半再多加3个,每次在偶数天吃剩余桃的总数的一半再多吃一个。
* 请输入一个天数,使得该天数的剩余桃数正好为1,请打印出猴子第一天共摘了多少个桃?
*
* 问题分解:设输入的天数为n; 首先,找到问题入口:
* 第1天时, 求剩余桃数:F(1) ; 其次,找到问题的递归方程,
* 在本题中,递归方程有两个:
* 若n天为奇数天:有F(n) = (F(n+1)+3)*2
* 若n天为偶数天:有F(n) = (F(n+1)+1)*2
* 其中,奇数天与偶数天相互递归。
* 最后,问题结束:当程序求到第n天时,F(n) = 1 已知。
*
*/
public static void main(String[] args) {
System.out.println("请输入天数:");
Scanner scanner = new Scanner(System.in);
int day = scanner.nextInt(); // 输入值
System.out.println(sumNum(day, 1));
}
private static int sumNum(int day, int count) {
if (count % 2 == 0) {
return sumEvenDay(day, count); // 偶数天数
} else {
return sumOddDay(day, count); // 奇数天数
}
}
private static int sumOddDay(int day, int count) {
if (count == day)
return 1;
else
return 2 * (sumEvenDay(day, count + 1) + 3);
}
private static int sumEvenDay(int day, int count) {
if (count == day)
return 1;
else
return 2 * (sumOddDay(day, count + 1) + 1);
}
}
递归算法求解(4):爬楼梯台阶
递归函数有返回值的比没有返回值的麻烦一点,因为一个函数只有一个返回值,但是递归还要求有基础情形的存在,所以还必须有if判断来终止递归。所以在每一个if或者else后边都有一个return,这样保证函数在任何一种情况下都有且仅有一个返回值。
分析一下这个算法:
A:如果有0个台阶,那么有0种走法,这个不用多说;
B:如果有1个台阶,那么有1种走法;
C:如果有2个台阶,那么有2种走法(一次走1个,走两次;一次走两个);
以上的B和C就是基础情形。
D:接下来就是递归了,如果台阶数目多于2个,那么首先第一步就有两种选择:第一次走1个,或者第一次走两个。这样除了第一次后边的走法就有了两种情形:climbStairs(n-1)和climbStairs(n-2)。这样一直递归下去,直到出现到了基础情形(即n==1或n==2的情形),递归到这个地方(基础情形),然后开始回溯 ,这就是所说的和递归密切相关的“回溯”了。回溯,顾名思义就是从结果倒着回去,找到整个过程,进而分析这个路径或者说是实现的过程。
package com.bean.algorithmbasic;
public class ClimbStairs {
/*
* 递归求解爬楼梯
* 问题描述:已知一个楼梯有n个台阶,每次可以选择迈上一个或者两个台阶,求走完一共有多少种不同的走法。
* */
public int climbStairs(int n) {
int i = 1;
if (n <= 0)
return 0;
if (n == 1) {
return i;
}
if (n == 2) {
i++;
return i;
} else
return climbStairs(n - 1) + climbStairs(n - 2);
}
public static void main(String[] args) {
ClimbStairs cs = new ClimbStairs();
int a = cs.climbStairs(5);
System.out.println(a);
}
}
递归算法求解(5):采用递归算法解决全排列的问题
问题描述:
设R={r1,r2,…,rn}是要进行排列的n个元素,Ri=R-{ri}。
集合x中元素的全排列记为Perm(X)。
(ri)Permutation(X)表示在全排列Permutation(X)的每一个排列前加上前缀ri得到的排列。
R的全排列可归纳如下:
当n=1时,Permutation(R)=(r),其中r是集合中唯一的元素;
当n>1时,Permutation(R)由(r1)Permutation(R1),(r2)Permutation(R2),(r3)Permutation(R3)……(rn)Permutation(Rn)构成。
package com.bean.algorithmbasic;
public class RecursionDemo {
/*
* 采用递归算法解决全排列的问题
*
* 设R={r1,r2,...,rn}是要进行排列的n个元素,Ri=R-{ri}。
* 集合x中元素的全排列记为Perm(X)。
* (ri)Permutation(X)表示在全排列Permutation(X)的每一个排列前加上前缀ri得到的排列。
* R的全排列可归纳如下:
* 当n=1时,Permutation(R)=(r),其中r是集合中唯一的元素;
* 当n>1时,Permutation(R)由(r1)Permutation(R1),(r2)Permutation(R2),(r3)Permutation(R3)......(rn)Permutation(Rn)构成。
*
* */
public static void permutationNumbers(int[] list, int k, int m) {
if (k == m) {
for (int i = 0; i <= m; i++) {
System.out.print(list[i]+"\t");
}
System.out.println();
} else {
for (int i = k; i <= m; i++) {
swap(list, k, i);
permutationNumbers(list, k + 1, m);
swap(list, k, i);
}
}
}
public static void swap(int[] list, int a, int b) {
int temp;
temp = list[a];
list[a] = list[b];
list[b] = temp;
}
public static void main(String args[]) {
int[] list = new int[5];
for (int i = 0; i < list.length; i++) {
list[i] = i + 1;
}
permutationNumbers(list, 0, list.length - 1);
}
}
(完)