递归算法设计方法

901 阅读5分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

递归算法设计方法

由于二叉树是一种递归数据结构,后面涉及大量的二叉树递归算法设计,专门介绍递归算法设计的一般方法。

1.1 什么是递归

在定义一个过程或函数时如果出现调用本过程或本函数的成分,称为递归。若直接调用自身,称为直接递归。若过程或函数p调用过程或函数q,而q又调用p,称为间接递归。这里主要介绍直接递归。例如,有以下递归函数:

      f(1)=1
      f(2)=1
      f(n)=f(n—1)+f(n—2)  n≥3

对应的求解fn)的递归算法fun()如下。

计算fun(5)的过程如图6.16所示,图中向下箭头(用实线表示)表示递推或分解过程,向上箭头(用虚线表示)表示求值或返回过程,fun(n)上方的值表示其求解结果。

图6.16 fun(5)的计算过程

将前面的f()函数的描述形式称为递归模型,fun()是对应的递归算法。从中看到递归模型是递归算法的抽象,它反映了一个递归问题的递归结构。

一般地,递归模型由两部分组成,一部分为递归出口,它给出了递归的终止条件,例如,前面例子中的f(1)=1和f(2)=1就是递归出口;另一部分为递归体,它确定递归求解时的递推关系,例如,前面例子中的fn)=fn—1)+fn—2)就是递归体。

递归算法求解过程的特征是:先将不能直接求解的问题转换成若干个相似的小问题,通过分别求解各子问题,最后获得整个问题的解。当这些子问题不能直接求解时,还可以再将它们转换成若干个更小的子问题,如此反复进行,直到遇到递归出口为止。这种自上而下将问题分解、求解,再自下而上引用、合并,求出最后解答的过程称为递归求解过程。这是一种分而治之的算法设计方法。

1.2 递归算法设计一般方法

递归算法的设计方法是,先确定对应的递归模型,再将其转换为递归算法。其核心思想是把问题简化分解为几个子问题,其中子问题的形式和算法与原问题算法相似,只是比原来简化。

因此,在设计递归算法时,应着重分析递归数据结构的递归运算,充分利用这类运算的特性进行递归设计。获取求解问题的递归模型的一般步骤如下。

(1)对原问题fs)进行分析,假设出合理的“小问题”fs′)(与数学归纳法中假设n=k—1时等式成立相似);

(2)假设fs′)是可解的,在此基础上确定fs)的解,即给出fs)与fs′)之间的关系(与数学归纳法中求证n=k时等式成立的过程相似);

(3)确定一个特定情况(如f(1)或f(0))的解,由此作为递归出口(与数学归纳法中求证n=1或n=0时等式成立相似)。

例6.7】设计一个递归算法求一个整数数组中所有元素之和。

:设fai)为整数数组aa[0]~a[i—1]这i个元素之和,这是大问题;小问题为fai—1),它为a[0]~a*[i—2]这i—1个元素之和。假设fai—1)已求出,则有fai)= fai—1)+a[i—1],另外,fa,1)=a[0],即一个元素和等于这个元素值。对应的递归模型如下。

      f(a,1)=a[0]
      f(a,i)=f(a,i1)+a[i—1]i >1

相应的递归算法如下。

      int fun(int a[],int i)
      {    if(i==1) return a[0];
           else return (fun(a,i—1)+a[i—1]);
      }

例6.8】对于不带头结点的非空单链表L(结点类型用SLinkNode表示),其结点值均为整数且所有结点值不相同,设计以下递归算法。

(1)求最大的结点值;

(2)求最小的结点值;

(3)正向输出所有结点值;

(4)反向输出所有结点值。

:(1)对于不带头结点的单链表LL是第一个数据结点的指针,L—>next也是一个不带头结点的单链表,它仅比单链表L少一个结点。设f1(L)计算单链表L中最大结点值,这是原问题,小问题为f1(L—>next),它计算单链表L—>next中最大结点值,如图6.17所示。

图6.17 不带头结点的单链表

假设f1(L—>next)已计算出来,显然有f1(L)=max(L—>data,f1(L—>next));当单链表L中只有一个结点时有:f1(L)=L—>data。对应的递归模型如下。

对应的递归算法如下。

(2)分析同(1)小题。对应的递归算法如下。

(3)设f3(L)正向输出单链表L的所有结点值,这是原问题。小问题为f3(L—>next),它正向输出单链表L—>next的所有结点值。假设f3(L—>next)已输出,显然f3(L)等价于先输出L—>data值,再调用f3(L—>next)。当单链表L为空时,不做任何输出。对应的递归模型如下。

其中,“≡”表示等价关系,对应的递归算法如下。

(4)分析同(3)小题。对应的递归算法如下。

从以上算法设计看出,递归体的微小变化,会导致递归算法执行结果的大相径庭。如(3)和(4)算法中仅两个语句的次序颠倒,结果是前者正向输出所有结点值,后者反向输出所有结点值。

说明:对单链表设计递归算法时通常采用不带头结点的单链表。因为带有头结点,会导致LL—>next结构不一致(前者有头结点,后者没有头结点)。