分治策略
分治策略是将规模比较大的问题可分割成规模较小的相同问题。问题不变,规模变小。这自然导致递归过程的产生。分支与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
分治法所能解决的问题一般具有以下四个特征
- 1、该问题的规模缩小到一定程度就可以容易的解决。
- 2、该问题可以分解为若干个规模较小的相同问题。
- 3、使用小规模的解,可以合并成该问题原规模的解。
- 4、该问题所分解出的各个子规模是相互独立的。
分治法步骤
在分支策略中递归的求解一个问题,在每层递归中应用如下三个步骤:
- 1、分解:将规模划分成一些更小的规模的原问题。只是问题相同,规模更小,且不允许对相同问题规模重复进行求解。
- 2、解决:递归的求解子问题。如果子问题的规模足够小,则停止递归,直接求解。
- 3、合并:将小规模的解合并为原规模问题的解。
示例:求解n的阶乘(不考虑int溢出)
分析:
阶乘可递归的定义为:
代码:
用循环完成:
public static int fac_ForLoop(int n){
int sum = 0;
for(int i = 1;i <= n;++i){
sum = sum + i;
}
return sum;
}
用递归完成:
public static int fac_ForRec(int n){
if(n <= 1){
return 1;
}else {
return fac_ForRec(n-1) * n;
}
}
递归
若一个函数直接或间接的调用自己,则称这个函数是递归的函数。(简单的描述为“自己调用自己”)
递归函数的执行分为“递推”和“回归”两个过程,这两个过程由递归终止条件控制,即逐层递推,直至递归终止条件满足,终止递归,然后逐层回归。
递归调用同普通的函数调用一样,每当调用发生时,就要分配新的栈帧(形参数据,现场保护,局部变量);而与普通的函数调用不同的是,由于递归的过程是一个逐层调用的过程,因此存在一个逐层连续的分配栈帧过程,直至遇到递归终止条件时,才开始回归,这时才逐层释放栈帧空间,返回到上一层,直至最后返回到主调函数。
图解递归过程(代码的调动过程)
图解递归过程(栈帧的动态调动过程)
总结
- 函数被调用,不管自己调用自己,还是被其他函数调用,都将会给被调用函数分配栈帧。
- 不存在无穷递归。即递归函数必须要有一个是递归结束的出口(要有递归终止的条件语句)。
- 问题的规模不要过大,递归过深,引起栈溢出。
注意事项
- 1、限制条件:在设计一个递归过程时,必须至少有一个可以终止此递归的条件,并且还必须对在合理的递归调用次数内未满足此类条件的情况进行处理。如果没有一个在正常情况下可以满足的条件,则过程将陷入执行无限循环的高度危险之中。
- 2、内存使用:应用程序的局部变量所使用的空间有限。过程在每次调用它自身时,都会占用更多的内存空间以保存其局部变量的附加副本。如果这个进程无限持续下去,最终会导致StackOverFlowException错误。
- 3、效率:几乎在任何情况下都可以用循环替代递归。循环不会产生传递变量、初始化附加存储空间和返回值所需的开销,因此使用循环相对于使用递归调用可以大幅提高性能。
- 4、相互递归:如果两个过程相互调用,可能会使性能变差,甚至产生无限递归。此类设计所产生的问题与单个递归过程所产生的问题相同,但更难检测和调试。
- 5、调用时使用括号:当Function过程以递归方式调用它自身时,你必须在过程名称后加上括号(即使不存在参数列表)。否则,函数名就会被视为表示函数的返回值。
- 6、测试:在编写递归过程时,应非常细心的进行测试,以确保它总是能满足某些限制条件而终止递归过程。你还应该确保不会因为过多的递归调用而耗尽内存。
常见问题
- 整形无序数组,完成查询操作
/**
* 整形无序数组,循环完成查询操作
*/
public static int Find_Ar_ForLoop(int[] arr,int value){
for(int i = 0;i < arr.length