标签:
对于强大的递归。要想做到灵活运用,是需要花时间进行练习并总结。往往递归学习的入门也是难度也比较大,常常会处于看得明,却写不出的"尴尬"情况。
将一个大的问题分解成比较小的、有着相同形式的问题。
递归是一种强有力的思想。在计算机科学的学习中,一个重要的必须学习的概念是递归。递归是一种编程策略,它把一个大的问题分解成具有相同形式的简单问题。
一般对递归思想的介绍,都是说将大问题分解为一个个小问题。本人觉得,带着 “如何将问题规模缩少”的思想 比 “将大问题分解为一个个小问题” 的思想要更好地编写递归程序。
当尝试理解递归程序时,必须能够抛开底层的细节,将注意力集中在单个计算层次上。在这个层次上,只要一个递归调用的参数在某些方面能比前一个参数更简单,那么就可以认为任何递归调用都能够自动地得到正确的答案。这种心理策略——假设任何更简单的递归都能正确地实现——叫做对递归跳跃的信任。在实际应用中,学习应用这个策略是使用递归的基础。
保持整体观:递归思维要求整体考虑。在递归领域中,只考虑局部是理解的敌人,将会妨碍对递归的理解。为了保持这种整体观,必须习惯于采用对递归跳跃的信任。无论是在写递归程序或是理解递归程序,都必须达到忽视单个递归调用细节的地步。只要选择了正确的分解,确认了相应的简单情景,并且正确地实现了策略,那么这些递归调用能够自己运行,不必过多考虑。
在示例练习中会尽量以上述几点为基础去思考问题的解法
//对许多人而言,理解递归的最好方法是从简单的数学函数开始。 //因为数学函数中递归结构直接能从问题的陈述中得到,并且可以很容易地看到。 //在这些数学函数中,最常见的就是阶乘函数——在数学中的传统表示为 n! //——它被定义为 1 到 n 之间的所有整数的连乘积。 //(当然,此题的解法用迭代也能轻松解决) public class Factorial { public static int factorial(int n) { if (n < 0) { return 0; } if (n == 0 || n == 1) { return 1; } return n * factorial(n - 1); } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(factorial(-1)); System.out.println(factorial(0)); System.out.println(factorial(5)); } }
斐波那契数列指的是这样一个数列:0、1、1、2、3、5、8、13、21、……
在数学上,斐波纳契数列以如下被以递归的方法定义:
F(0)=0,
F0=1,
Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
问题:输入 n,求斐波那契数列第n个数
解法:递归
Fn=F(n-1)+F(n-2) 像这种类型的表达式,序列中的每一个元素都由先前的元素来确定,这种序列被称为递归关系
有了递归分解式,还需简单情景进行结束递归。
简单情景:
观察可知,当n > 3 时,每项的值为前两项之和。即当n = 1 和 n = 2 时分别取值为0、1
public class Fibonacci { public static int fibonacci(int n) { if (n <= 0) { return 0; } if (n == 1 || n == 2) { return 1; } return fibonacci(n - 1) + fibonacci(n - 2); } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(fibonacci(-1)); System.out.println(fibonacci(0)); System.out.println(fibonacci(1)); System.out.println(fibonacci(2)); System.out.println(fibonacci(5)); System.out.println(fibonacci(10)); } }
所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的。比如"level" 、 “aaabbaaa”
题目:判断一个字符串是否为回文
解法:递归
递归的作用在于把问题的规模不断缩少,直到问题缩少到能简单地解决
问:如何缩少问题规模?
答:通过观察可以知道,一个回文字符串其中内部也是回文。所以,我们只需要以去掉两端的字符的形式一层层检查,每一次的检查都去掉了两个字符,这样就达到了缩少问题规模的目的。
新问题与原问题有着相同的形式
当去掉两端字符后的字符串,其产生的新问题同样是检查这个字符串是否回文。
递归的结束需要简单情景
1. 字符串长度可能会奇数或偶数:
2. 如果检查到两端两个字符不相同。则说明此字符串不是回文,直接返回0,不需要继续检查
递归跳跃的信任
此题的递归分解比较简单,所以对在递归过程中细节的实现,我们可以直接看出。但是一些较复杂的题目上,我们就没那么容易看出过程中细节的实现,这时候就需要我们递归跳跃的信任!
public class PalindromeString { public static boolean isPalindrome(String str) { if (str == null || str.length() == 0) { return false; } int begin = 0; int end = str.length() - 1; int length = str.length(); return is_Plindrome(str, begin, end, length); } private static boolean is_Plindrome(String str, int begin, int end, int length) { if (length == 0 || length == 1) { return true; } if (str.charAt(begin) == str.charAt(end)) { return is_Plindrome(str, begin + 1, end - 1, length - 2); } else { return false; } } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(isPalindrome("a")); System.out.println(isPalindrome("aa")); System.out.println(isPalindrome("ab")); System.out.println(isPalindrome("ab***************ba")); } }
字符串翻转:将字符串 test 翻转,变为 tset
解法:递归
此题的递归跟判断回文字符串的解法原理一样。只是不是比较两端字符,而是直接交换。
public class ReverseString { public static String reverseString(String str) { String reString = new String(); if (str == null || str.length() == 0) { return reString; } if (str.length() == 1) { return str; } int length = str.length(); int begin = 0; int end = length - 1; char[] chararray = str.toCharArray(); reverse_string(chararray, begin, end, length); return String.copyValueOf(chararray); } private static void reverse_string(char[] chararray, int begin, int end, int length) { if (length == 0 || length == 1) { return; } char temp = chararray[begin]; chararray[begin] = chararray[end]; chararray[end] = temp; reverse_string(chararray, begin + 1, end - 1, length - 2); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(reverseString("b")); System.out.println(reverseString("ab")); System.out.println(reverseString("abca")); System.out.println(reverseString("987654321")); } }
假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
折半(二分)查找也是一个分而治之策略的完美示例。所以,其可以用递归实现也不足为奇。
解法:递归
递归的作用在于把问题的规模不断缩少,直到问题缩少到能简单地解决
由于我们查找的须是一个有序表,所以在每一次的比较中,缩少的规模将是上一次查找规模的一半,可见这效率是相当大
新问题与原问题有着相同的形式
新问题与原问题具有相同的形式,就是查找某个字符
递归的结束需要简单情景
递归跳跃的信任
由于实现细节较易就能看出,未能突出体现出递归跳跃的信任的重要性
public class BinarySearch { public static int binarysearch(int[] array, int key) { // 查找失败返回-1 if (array == null || array.length == 0) { return -1; } int begin = 0; int end = array.length - 1; int mid = (begin + end) >> 1; return binary_search(array, key, begin, end, mid); } private static int binary_search(int[] array, int key, int begin, int end, int mid) { // 查找失败返回-1 if (begin > end) { return -1; } if (array[mid] == key) { return mid; } else if (array[mid] > key) { end = mid - 1; mid = (begin + end) >> 1; return binary_search(array, key, begin, end, mid); } else if (array[mid] < key) { begin = mid + 1; mid = (begin + end) >> 1; return binary_search(array, key, begin, end, mid); } // 查找失败返回-1 return -1; } public static void main(String[] args) { // TODO Auto-generated method stub int[] testarray = { 1, 2, 3, 4, 5, 8000 }; System.out.println(binarysearch(testarray, 5)); System.out.println(binarysearch(testarray, 8100)); System.out.println(binarysearch(testarray, 8000)); System.out.println(binarysearch(testarray, -1)); } }
交互递归
到目前为止,看到的递归函数都是直接调用自己。虽然大多数的递归函数都符合这一形式,但其实递归的定义更为广泛,如果某个函数被细分成了几个子函数,那么可以在更深的嵌套层次上应用递归调用。例如:如果函数 f 调用函数 g ,而函数 g 反过来又调用函数 f ,这些函数的调用仍然被看作是递归。这种类型的递归被成为交互递归
下面通过判断一个数是偶数还是奇数来展示交互递归的应用,并且此题突出了递归跳跃的信任的重要性
首先,先看奇数和偶数的描述:
递归跳跃的信任
从代码可以看出,代码的实现是完全基于上面奇数和偶数的描述的三点。初看,这是多么的不可思议。如果想要探索其底层是如何实现的,也只需用一个较少的数字代入,跟踪调用验证就OK
如单纯地从表面看,单凭 “定义0是偶数” 这个简单情景真的没法看出这递归竟然能正确工作。所以,对于没法一下子就能看出的这种情况,我们需要的就是递归跳跃的信任,只要我们递归分解正确和简单情景分析正确,实现细节就不必去担心,交给计算机。也因此,只要掌握了递归的思维,解决一个问题是多么简单和快捷。
public class Odd_OR_Even { public static boolean isodd(int n) { return !(iseven(n)); } public static boolean iseven(int n) { if (n == 0) { return true; } else { return isodd(n - 1); } } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(isodd(5)); System.out.println(iseven(5)); } }
放苹果
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 16055 Accepted: 10133
Description
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
Input
第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
Output
对输入的每组数据M和N,用一行输出相应的K。
Sample Input
1
7 3
Sample Output
8
Source
lwx@POJ
public class PutApple { public static int Apple_Put(int m, int n) { // 当苹果已全部放满时 if (m == 0) return 1; // 当盘子剩下一个时 if (n == 1) return 1; // 当m<0时候的放法包含在fun(m,n-1)中 if (m < 0) return 0; return Apple_Put(m - n, n) + Apple_Put(m, n - 1); } /** * @param args */ public static void main(String[] args) { )); System.out.println(Apple_Put(2, 2)); System.out.println(Apple_Put(7, 3)); } }
public class PutApple { private static int f[][] = new int[11][11]; public static int ApplePut(int m, int n) { if (f[m][n] != 0) { return f[m][n]; } else if (m == 1 || n == 1) { f[m][n] = 1; return f[m][n]; } else if (m < n) { f[m][n] = ApplePut(m, m); return f[m][n]; } else if (m == n) { f[m][n] = 1 + ApplePut(m, m - 1); return f[m][n]; } else { f[m][n] = ApplePut(m - n, n) + ApplePut(m, n - 1); return f[m][n]; } } public static void main(String[] args) { System.out.println(ApplePut(1, 5)); System.out.println(ApplePut(5, 1)); System.out.println(ApplePut(3, 3)); System.out.println(ApplePut(7, 3)); } }
标签:
原文地址:http://blog.csdn.net/f2006116/article/details/51601982