【算法】递归:从分治法理解递归

【算法】递归:从分治法理解递归

导语:最近又遇见了许多关于递归的题目,回忆最开始学习递归的时候,自我感觉是比较困难的,可是现在再写递归题时,发现轻松了许多。我想主要原因就是学习分治法的思想之后,更好的理解了递归的原理,总结相应的基本思想,希望通过这篇文章分享给大家。

摘要:文章将从分治法的基本思想展开,再通过该思想去理解递归的过程与原理,总结基本方法,最后通过举例说明来给读者演示解题过程。


一. 分治法

摘抄自《算法导论》,其中介绍分治法的基本思想是:将原问题分解为几个规模较小但相识于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。

而分治模式的三个步骤如下:

  • 分解:将原问题分解为若干子问题,这些子问题是原问题的规模较小的实例;
  • 解决:解决这些子问题,递归地求解各子问题,当问题足够小时,直接求解;
  • 合并:合并这些子问题得到原问题的解;

简单来说就是将问题分解为类似小问题,然后分解到足够小可以求解后,完成合并。在解题的过程中,我们就可以通过这样的过程来解决递归问题。

二. 递归问题求解

所谓递归,可以拆分成递和归,递就是通过分解把问题化小,直到小到可以解决,而归就是合并,把解决后的问题回归合并。可是递归什么时候递,什么时候归呢,因此我认为递归需要两个条件,一个是让它递的过程,这就是分解的过程,而归就是设置终止条件。总结下来就是需要,完成对三个步骤的分析,以及对一个终止条件的书写。所以现在我们同样可以通过以上三个步骤以及一个终止条件来解决两道经典的递归问题,加深理解并总结方法。

  1. 归并排序

    归并排序是一个排序算法,主要思想是将序列分为两组,分别再进行归并排序,再将排序好的序列一一整合,这是一种使用递归的排序算法。

    了解大概思想后,我们解决该问题,通过三个步骤与一个条件进行分析:

    • 分解:将序列拆为两组,作为子序列;

    • 解决:使用归并排序继续为子序列分组排序;

    • 合并:将排序好的数组合并即可

    • 一个条件:但分解为只有子序列长度为1时,就可以结束递的过程;

    为了更好的理解,我们通过画图来进行分析:

    上图表示的就是递归排序的递的过程,也就是分解的过程,就是将序列分为两个子序列,再进行递归排序,我们知道,当只有子序列长度为1时是无需排序的,这也是我们的一个条件,所以我们将其返回。

    这里的归的部分就是整合,整合后的部分通过字体标红,通过不断的整合,我们最后可以得到原问题,即将得到一个排序好的数组。

    最终我们完成了三个步骤与一个条件的分析,根据以上分析:我们可以写出伪代码

    List mergesort(List inlist) {
      //终止条件:长度为 1 就返回子序列
      if (inlist.length() <= 1) return inlist;
      //分解过程:分为两个子序列
      List l1 = half of the items from inlist;
      List l2 = other half of items from inlist;
      // 合并过程(其中包含解决过程)
      return merge(mergesort(l1), mergesort(l2));
    }
    

    这样我们就可以通过解决了归并排序的问题,由于这里不是主要介绍递归算法,所以我们不对其中的细节进行概述,不过我会将代码放入gitee和下方,需要可以自取。其中的细节就是在实现合并的过程中,我使用了一个头尾指针来完成整合,是一种不错的整合方法。代码如下:

    void mergesort(int A[], int temp[],int left, int right) {
        //终止条件
    	if (left == right) return;    
        //分解与解决过程
    	int mid = (left + right) / 2;
    	mergesort(A, temp, left, mid);
    	mergesort(A, temp, mid + 1, right);
    	for (int i = left; i <= right; i++) // Copy
    		temp[i] = A[i];
        //合并过程
    	int i1 = left; int i2 = mid + 1;
    	for (int curr = left; curr <= right; curr++) {
    		if (i1 > mid)      // Left exhausted
    			A[curr] = temp[i2++];
    		else if (i2 > right)  // Right exhausted
    			A[curr] = temp[i1++];
    		else if (temp[i1] < temp[i2])
    			A[curr] = temp[i1++];
    		else A[curr] = temp[i2++];
    	}
    }
    
    1. 汉诺塔问题

    题目介绍:在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
    (1) 每次只能移动一个盘子;
    (2) 盘子只能从柱子顶端滑出移到下一根柱子;
    (3) 盘子只能叠在比它大的盘子上。

    请问将第一根柱上的N个盘移动到第三根柱上如何操作?

    我们围绕着三个步骤和终止条件进行分析:

    首先由数量从少到多入手,当柱子只有一个盘的时候,我们只需要将盘移动到目的柱子即可,这里只需一步操作:

    然后,但盘数为2时,我们需要先将上面的盘移动到辅助柱子,再将最底盘移动到目的柱子,最后再将辅助柱子上的盘移动到目的柱子上,就完成了:

    当盘数为n时,我们不难发现,要满足题意就必须先将最后一个盘从,源柱子移动到目的柱子,而为了达到这个目的,其他的圆盘必须升序的堆放在辅助柱子上,总结下来就是将n-1一个盘子移动到辅助柱子,再将第n个盘子移动到目的柱子:

    分析到这里,我们仿佛可以看见我们该如何分解了,在盘数为n的分析后,再将辅助柱子和源柱子交换,因为目的柱子上的盘子是最大盘,可以忽略,我们移动后并忽略后,发现这就是一个相似的子问题:

    因此我们可以得出三个步骤:

    • 分解:n个盘移动问题分解为n-1个盘移动到辅助柱子问题+对第n个盘移动到目的柱子问题
    • 解决:通过递归来解决n-1个盘的移动问题;
    • 合并:将1 - n 个盘子的操作组合起来
    • 终止条件:当只有一个盘的时候只需要将第一根柱子上的盘子移动到第三根柱子上

    经过以上分析后,我们编写代码:

    void Tower_Hanoi(int n,int source,int assit,int destination) {
        // 终止条件
    	if (n == 1)
    		printf("%d ----> %d\n",source,destination);
    	else {
            //分解:
            //将第n-1个盘子移动到辅助柱子
    		Tower_Hanoi(n - 1, source, destination,assit);
            //将第n个盘子移动到目的柱子
    		Tower_Hanoi(1, source, assit, destination);
            //解决:
            //通过递归解决n-1个盘子的移动问题
    		Tower_Hanoi(n - 1, assit, source, destination);
            //合并:为从每个步骤的输出过程
    	}
    }
    

三. 总结

以上我们通过分治法与递归的特性,总结了三个步骤与一个终止条件的解题框架。而要写出这个解题框架,最主要的还是分析过程,一般的分析过程,往往有着许多技巧,对于我的习惯来说,我会先从少数入手去寻求终止条件和基本解题过程,如何再从多数抽象去寻找解决原问题的相似子问题,从而得知如何去分解原问题。在通过分解后问题去思考如何解决这个子问题,以及解决完子问题后,如何去合并为原问题的解。

还有就是这三个步骤以及一个终止条件其实只是对分治法与递归特性的抽象,不是所谓的模板,更重要的是根据这些总结的方法去分析问题,灵活应用,不可生搬硬套。

最后,还要提到的是关于递归的内容还未结束,在往后一篇文章将会从斐波那契数列进行介绍,希望大家可以根据本篇的方法去试着实现一下。更重要的是后续还会对递归的算法进行性能分析,以及提供优化方法,敬请期待!

补充:

  1. 这是作者关于算法的第一篇文章,在书写过程中,作者也对自己的表达不是很满意,可能在有些表达上并不是非常清晰,如有错误或者不明之处,可以指出,非常感谢;
  2. 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
  3. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fat one

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值