算法(1)——递归与分治

任何一个可以用计算机求解的问题所需的计算都与其规模n有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需要任何计算;n=2时,只要作一次比较即可排好序;n=3时只要作3次比较即可……。当n比较大的时候,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。分治的设计思想是,讲一个难以解决的大问题分割成一些规模较小的相同问题,以便各个击破,分而治之

凡治众如治寡,分数是也。

         
                            —-孙子兵法

如果原问题可分割成K个子问题(1<k<=n),且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接解决求出其解,由此自然导致递归算法的产生。分治与递归像是一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。

程序直接或间接调用自身的编程技巧成为递归算法。

递归算法是一个过程或函数在其定义或说明中又直接或间接调用的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归策略只需少量代码就可以描述出解题过程所需要的多次重复计算,大大地减少了代码的代码量。

递归的优势在于用有限的语句来定义对象的无线集合。

用递归思想写出的程序往往十分简洁易懂。

一般来说,递归需要有边界条件、递归前段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。注意,在使用递归策略时,递归结束条件,称为递归出口,否则递归将无限进行下去(死锁)。

递归算法一般用于解决三类问题:

(1)数据的定义是按递归定义的。

(2)问题解法用递归算法实现,如回溯算法。

(3)数据结构形式是按递归定义的。如树的遍历,图的搜索。

递归的缺点

递归算法解题的运行效率较低。在递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。

递归算法是解决问题的一种最自然且合乎逻辑的方式,利用递归算法不需要花费太多精力就能解决问题,但是程序的执行效率可能会变差。在这种情况下,通常把递归转换为非递归算法。

解决方法
在递归算法中消除递归调用,转化为非递归算法。
1.采用一个用户定义的栈来模拟系统的递归调用工作栈。该方法通用性强,但本质上还是递归,只不过人工做了本来由编译器做的事情,优化效果不明显。
2.用递推来实现递归函数。
3.通过Cooper变换、反演变换能将一些递归转化为尾递归,从而迭代求出结果。
后两种方法在时空复杂度上均有较大改善,但其适用范围有限。

分治法所能解决的问题一般具有以下几个特征:
该问题的规模缩小到一定的程度就可以容易地解决;
该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
利用该问题分解出的子问题的解可以合并为该问题的解;
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划

 divide-and-conquer(P)
{
if ( | P | <= n0) adhoc(P);   //解决小规模的问题
divide P into smaller subinstances P1,P2,…,Pk//分解问题
for (i=1,i<=k,i++)
      yi=divide-and-conquer(Pi); //递归的解各子问题
return merge(y1,…,yk); //各子问题的解合并为原问题的解
 
 }
    用分治法设计算法,最好使子问题的规模大致相同。即将一个问题分成大小相等的k个子问题的处理方法是有效的。
    
    使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,总比子问题规模不等的做法要好。

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/zd2014zd/article/details/48488811
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞