在前面的随笔中其实谈到了一些递归分治的算法,也以为自己写上去了,今天在看到没有写。下面就来补上。
递归分治是算法中比较重要的思想。在之前也聊到了递归和递推的区别。递归这里就不再详细讲述了。下面讲一讲分治。分治其实很简单,就是将一个比较大的问题分解为很多的小问题,解决小问题的最优解比解决大问题的最优解要简单的多,很多的小问题的最优解就组成了大问题的最优解。从这里也可以发现,递归和分治在一起用的话会比较好,同时也是容易想到的。
下面讲一讲递归的一些我会的东西,递归就是间接和直接调用自身的算法。比如斐波那契数列就是用递归比较简单解决的问题之一。递归有三个要素,递归主体,递归边界,递归参数。这三个要素组成了递归。先讲一讲递归的主体,递归的主体算法就是递归的主体,这一部分是比较重要的,因为递归的大部分操作都在这里实现。递归边界就是递归的尽头,因为任何一个算法都要在有限的时间内得出结果,否则就是失败的,递归的边界就给递归提供了一个结束的具体条件。递归的参数是每次递归发生变化的数值,有时为了输入的方便,递归的参数会有一部分参数没有发生变化,这也是可以的。
纸上谈兵终究学不会,下面就来具体就问题来详细探讨一下。
先讲一个简单的,容易想的。“最长公共子序列”问题,最长公共子序列问题可能大家用动态规划做的比较多,因为用动态规划比递归要简单的多。但是最长公共子序列也可以用递归来做。下面就来讲一讲具体的实现,最长公共子序列问题就是在两串字符串中找出来两个字符串都具有的子序列(子序列可以间断,但是顺序不可以乱)。问题了解了,谈谈思路吧,分治的思想就是将问题分为小问题,我们将问题分解为一个字符的比较,这个就简单了。同时也可以用递归来将剩余的问题交给下一个自身算法来解决。这样递归的主体就是解决一个字符的比较。但是下面遇到了一个问题,当前比较的字符一样时还可以往下面递归,但是不一样时该怎么递归呢?我们可以将A,B比作两个字符串,i表示当前比较的字符,i-1就是下一个字符的位置。这样当Ai==Bi时,递归就是Ai-1和Bi-1的比较。当Ai和Bi不一样时,就可以分为Ai与Bi-1和Ai--1与Bi的比较,这样,还是递归到了下一个子问题。比较两者的大小,选出大的就可以了。
递归的主体解决了,下面解决递归的边界。递归的边界可能大家想到了当i==0时,就结束了,这个比较好想到,但是其他的问题递归的边界可能就不是很好想了。递归的参数就呼之欲出了,两个字符串的比较位置就是参数(也可以将字符串比较一次直接删除比较的元素),这里要注意的就是递归常用的一个方法,试探与回溯,因为算法不可能直接求出来最优解,需要算法不停的试探下一个结果是对还是错,同时还要回到算法开始的位置。(本题可能并不需要这种方法,因为并没有对字符串做出更改)。
上面就是最长公共子序列的递归算法的大致思路,这里因为一些原因(懒),这里就不在列出代码。上面可能就是一个大致的思路,具体的细节可能还会有一些不一样。但是大致一致。下面就练习一个简单的递归思路。
求出一个数组所有的元素和,这个问题很多人估计一个for循环就可以完成了,但是我们是用递归的思想来实现。下面来说一说思路。
先想一下递归的主体,我们可以计算当前数组位置的一个元素的和,然后将剩下的交给后面的递归算法来做。主体完成,递归的边界也就容易想到,当数组中的所有元素递归一遍后就是递归结束的边界。递归的参数前面也是很简单,当前累加的元素的位置就是参数,这里为了方便也将数组作为参数传了进来。
算法的实现我用了一个更加复杂的方式(顺便练一练二分),就是将递归的算法交给了两部分,只是计算了数组的中间位置(或者中间位置都没有计算)。下面就粘贴具体代码:
public class demo5 { public static int f(int a[],int end) { if(end<0) { return 0; } return f(a,end-1)+a[end]; } public static int f(int a[],int min,int max) { int mid=(max+min)/2; if(mid==max){ return a[max]; } return f(a,min,mid)+f(a,mid+1,max); } public static void main(String args[]) { int a[]={2,3,4,5,6,7,8,10}; int i=f(a,0,7); System.out.println(i); } }
代码没有什么难点,看看就明白了