首页 > 编程语言 > 详细

数据结构与算法-递归和回溯

时间:2021-02-06 23:57:37      阅读:44      评论:0      收藏:0      [点我收藏+]

第二章递归和回溯

基础引言

  • 什么是递归:任何调用自身的函数称为递归。
  • 为什么要用递归:一般来说,在编译或解释时,循环会转化为递归函数。当任务能够被相似的子任务定义时,采用递归处理十分有效。例如,排序、搜索和遍历等问题往往有简洁的递归解决方案。

递归函数的格式

理论

递归函数在执行一个任务时,需要调用函数自身来完成一些子任务。在某些时候,函数不需要继续调用函数自身就可以完成当前子任务。函数不再递归的情况称作基本情形(base case,也称为基本情况)。而函数调用自身来执行子任务的情况就称作递归情形(recursive case)

形式描述

技术分享图片

举例

技术分享图片

递归和内存(可视化)

理论

每次递归调用都在内存中生成一个新的函数副本(实际上仅仅是一些相关的变量)。
一旦函数结束(即返回某些数据),该返回函数的副本就从内存中删除。递归方案看起来简单,但是可视化和跟踪递归过程需要花费时间。为了更好地理解归递过程,考虑下面的例子。

技术分享图片

在这个例子中,假设当参数n-4时调用print函数,内存分配的可视化过程如下图所示。

技术分享图片

技术分享图片

递归与迭代

  • 递归

    • 当到达基本情形时,递归终止。
    • 每次递归调用都需要额外的空间用于栈帧(内存)开销。
    • 如果出现无穷递归,程序可能会耗尽内存,并出现栈溢出。
    • 某些问题采用递归方法更容易解决。
  • 迭代

    • 当循环条件为假时,迭代终止。
    • 每次迭代不需要任何额外的空间开销。
    • 由于没有额外的空间开销,所以若出现死循环,则程序会一直循环执行。
    • 采用迭代求解问题可能没有递归解决方案那样显而易见。

递归说明

  • 递归算法有两类情形:递归情形和基本情形。
  • 每个递归函数必须终止于基本情形。
    通常,迭代解决方案比递归解决方案更加有效(因为后者有函数调用的开销)。
  • 一个递归算法可以通过使用栈代替递归函数的方式来实现,但通常是得不偿失的。
    这意味着任何能用递归求解的问题也能用迭代来求解。
  • 对于某些问题,没有明显的迭代求解算法。
  • 有些问题非常适合用递归来求解,而有些则不适合。

递归算法的经典用例

  • 斐波那契数列、阶乘。归并排序、快速排序。
  • 二分查找。
  • 树的遍历和许多树问题:中序遍历、前序遍历、后序遍历。
  • 图的遍历:深度优先搜索、广度优先搜索。
  • 动态规划例子。
  • 分治算法。汉诺塔。
  • 回溯算法(将在下一节讨论)。

递归相关的问题

问题一:汉诺塔迷题
解析:

汉诺塔是一个数学谜题。

有3根柱子(或木桩、塔)和一些可以在柱子之间来回移动的不同大小的圆盘。

开始时,所有的圆盘按照从小到大的次序自上而下叠放在一根柱子上,形成一个圆锥结构。

现在要求把整叠圆盘移动到另一根柱子上,移动时要遵守下面的规则:

  • 每次只能移动一个圆盘。
  • 每次移动,只能移动柱子最上面的一个圆盘到另一根柱子(这根柱子上有可能已有圆盘)。
  • 任何时候不能出现大圆盘在小圆盘上方的情况。

算法思路:

  • 将源柱最上面的n-1个圆盘移到辅助柱。
  • 将第n个圆盘从源柱移到目的柱。
  • 将辅助柱的n-1个圆盘移到目的柱。
  • 源柱最上面的n-1个圆盘移到辅助柱又可以认为是一个新问题,并且可以用同样的方式解决。
  • 一旦能解决了只有3个圆盘的汉诺塔问题,那么这个算法可以求解任意数量圆盘的汉诺塔问题。
public static void HRT(int n, char frompeg, char topeg, char auxpeg) {
    if (n == 1) {
        System.out.println("移动:从" + frompeg + "到" + topeg);
        return;
    }
    //利用C做辅助柱将A上n-1个移动到B柱
    HRT(n - 1, frompeg, auxpeg, topeg);
    System.out.println("移动:从" + frompeg + "到" + topeg);
    //利用A做辅助柱将B上n-1个移动到C柱
    HRT(n - 1, auxpeg, topeg, frompeg);
}

问题二:给定一个数组,请用递归方法判定数组中的元素是否是有序的。

public static int isArrayInSortedOrder(int[] a, int index) {
    if (index == 1) {
        return 1;
    }
    return a[index - 1] <= a[index - 2] ? 0 : isArrayInSortedOrder(a,index-1);
}

回溯

什么是回溯

  • 回溯是一种采用分治策略进行穷举搜索的方法。
  • 有时求解一个问题的最好算法是尝试所有的可能性。这种方法通常很慢,但有标准工具能够辅助该过程。
  • ·工具:生成基本对象的算法,例如二进制串(n位二进制串有2"种可能性)、排列
    (n!)、组合(n!/r!(n-r)!),一般字符串(长度为n的k进制串有k"种可能性),等等。
  • 通过剪枝回溯可以加速的穷举搜索。

回溯的经典用例

  • 二进制串:产生所有的二进制串。
  • 生成k进制串。
  • 背包问题。广义字符串。
  • 哈密顿回路(参见第9章)。
  • 图着色问题。

回溯相关问题

  • 问题3:生成所有n位长的字符串。假设A[0..n-1]是一个大小为n的数组。
public static void Binary(int n,int[] a) {
    if (n < 1){
        for (int aa:a) {
            System.out.printf(aa+"");
        }
        System.out.println();
    }else {
        a[n-1]=0;
        Binary(n-1,a);
        a[n-1]=1;
        Binary(n-1,a);
    }
}
  • 问题4 生成长度为n的所有k进制串,串中每位的取值为0..k-1.
public static void kstring(int n,int k,int[] a){
    if (n < 1) {
        for (int aa : a) {
            System.out.printf(aa+"");
        }
        System.out.println();
    }else{
        for (int i = 0; i < k; i++) {
            a[n-1] = i;
            kstring(n-1,k,a);
        }
    }
}

数据结构与算法-递归和回溯

原文:https://www.cnblogs.com/shitouzi-shi/p/14383459.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!