这是我参与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
对应的求解f(n)的递归算法fun()如下。
计算fun(5)的过程如图6.16所示,图中向下箭头(用实线表示)表示递推或分解过程,向上箭头(用虚线表示)表示求值或返回过程,fun(n)上方的值表示其求解结果。
图6.16 fun(5)的计算过程
将前面的f()函数的描述形式称为递归模型,fun()是对应的递归算法。从中看到递归模型是递归算法的抽象,它反映了一个递归问题的递归结构。
一般地,递归模型由两部分组成,一部分为递归出口,它给出了递归的终止条件,例如,前面例子中的f(1)=1和f(2)=1就是递归出口;另一部分为递归体,它确定递归求解时的递推关系,例如,前面例子中的f(n)=f(n—1)+f(n—2)就是递归体。
递归算法求解过程的特征是:先将不能直接求解的问题转换成若干个相似的小问题,通过分别求解各子问题,最后获得整个问题的解。当这些子问题不能直接求解时,还可以再将它们转换成若干个更小的子问题,如此反复进行,直到遇到递归出口为止。这种自上而下将问题分解、求解,再自下而上引用、合并,求出最后解答的过程称为递归求解过程。这是一种分而治之的算法设计方法。
1.2 递归算法设计一般方法
递归算法的设计方法是,先确定对应的递归模型,再将其转换为递归算法。其核心思想是把问题简化分解为几个子问题,其中子问题的形式和算法与原问题算法相似,只是比原来简化。
因此,在设计递归算法时,应着重分析递归数据结构的递归运算,充分利用这类运算的特性进行递归设计。获取求解问题的递归模型的一般步骤如下。
(1)对原问题f(s)进行分析,假设出合理的“小问题”f(s′)(与数学归纳法中假设n=k—1时等式成立相似);
(2)假设f(s′)是可解的,在此基础上确定f(s)的解,即给出f(s)与f(s′)之间的关系(与数学归纳法中求证n=k时等式成立的过程相似);
(3)确定一个特定情况(如f(1)或f(0))的解,由此作为递归出口(与数学归纳法中求证n=1或n=0时等式成立相似)。
【例6.7】设计一个递归算法求一个整数数组中所有元素之和。
解:设f(a,i)为整数数组a中a[0]~a[i—1]这i个元素之和,这是大问题;小问题为f(a,i—1),它为a[0]~a*[i—2]这i—1个元素之和。假设f(a,i—1)已求出,则有f(a,i)= f(a,i—1)+a[i—1],另外,f(a,1)=a[0],即一个元素和等于这个元素值。对应的递归模型如下。
f(a,1)=a[0]
f(a,i)=f(a,i—1)+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)对于不带头结点的单链表L,L是第一个数据结点的指针,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)算法中仅两个语句的次序颠倒,结果是前者正向输出所有结点值,后者反向输出所有结点值。
说明:对单链表设计递归算法时通常采用不带头结点的单链表。因为带有头结点,会导致L和L—>next结构不一致(前者有头结点,后者没有头结点)。