讲义一 递归的消除
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
递归算法具有两个特性:
(1) 递归算法是一种分而治之、把复杂问题分解为简单问题的求解问题方法,对求解某些复杂问题,递归算法分析方法是有效的。
(2)递归算法的时间效率差,其时间效率低。
为此,对求解某些问题时,我们希望用递归算法分析问题,用非递归算法求解具体问题; 消除递归原因:
其一:有利于提高算法时空性能,因为递归执行时需要系统提供隐式栈实现递归,效率低,费时。
其二:无应用递归语句的语言设施环境条件,有些计算机语言不支持递归功能,如FORTRAN 、C 语言中无递归机制 。
其三,递归算法是一次执行完,这在处理有些问题时不合适,也存在一个把递归算法转化为非递归算法的需求。
理解递归机制,是掌握递归程序技能必要前提。消除递归要基于对问题的分析,常用的有两类消除递归方法。
一类是简单递归问题的转换,对于尾递归和单向递归的算法,可用循环结构的算法替代。 另一类是基于栈的方式,即将递归中隐含的栈机制转化为由用户直接控制的明显的栈。利用堆栈保存参数,由于堆栈的后进先出特性吻合递归算法的执行过程,因而可以用非递归算法替代递归算法。
在大量复杂的情况下,递归的问题无法直接转换成循环,需要采用工作栈消除递归。工作栈提供一种控制结构,当递归算法进层时需要将信息保留;当递归算法出层时需要从栈区退出信息。
栈及其应用
一.栈的特点:
栈是一种线性表,对于它所有的插入和删除都限制在表的
同一端进行,这一端叫做栈的“顶”,另一端则叫做栈的“底”,
其操作特点是“后进先出”。
二.栈的抽象数据定义:
1、栈的数组表示 — 顺序栈
s 为栈、p 为指向栈顶的指针
type
stack=record
data:array[1..m] of datatype;
p:0..m
end;
var
s:stack; 2、栈的链接表示 — 链式栈
bottom
当栈的容量无法估计时,可采用链表结构
--链式栈.
链式栈的栈顶在链头.
无栈满问题,空间可扩充.
进栈(插入)与出栈(删除)都在栈顶处执行.
三.栈的基本操作:
(1)进栈操作push(s,x):往栈中推入元素x的项目;
若p=m则write('overflow')
否则p:=p+1;s[p]:=x;
(2)出栈操作pop(s):将栈顶元素中弹出;
若p=0则write('underflow')
否则p:=p-1;
(3)读栈顶元素top(s,x):把栈顶元素的值读到变量x中,栈保持不变;
若p=0则write('error')
否则x:=s[p];
(4)判栈是否为空sempty(s):这是一个布尔函数,当栈sp中没有元素(即t=0)时,称它为空栈,函数取真值,否则值为假。
若p=0则sempty:=true
否则sempty:=false;
(5)链式栈的进栈、出栈操作
进栈:数据元素进栈时,先生成一个新结点P,置数据域为X、指针域指向原栈顶结点,栈顶结点指向P。(在链头插入一个新结点)
出栈:先从栈顶取出数据元素至X,然后把S结点指到它的直接后继结点,原S结点清空。(在链头删去一个结点)
例9、Ackermann函数
[问题描述]
已知Ackermann函数定义如下:
1、手工计算Ack(3,2) 和Ack(3,6)。
解答:29和509
2、写出计算Ack(m,n)的递归算法程序。
program ackermann1;
var m,n:longint;
function ack(m,n:longint):longint;
begin
if m=0
then ack:=n+1
else if n=0
then ack:=ack(m-1,1)
else ack:=ack(m-1,ack(m,n-1))
end;
begin
write('Input m,n:');
readln(m,n);
writeln(ack(m,n))
end.
3、写出计算Ack(m,n)的非递归算法程序。
program ackermann2;
type stack=array [1..8000,1..2] of longint;
var m,n,top:longint;
s:stack;
begin
write('Input m,n:');
readln(m,n);
s[1,1]:=m; s[1,2]:=n;
top:=1;
while top>0 do
begin
m:=s[top,1];
n:=s[top,2];
top:=top-1;
if (top=0) and (m=0) then
begin writeln(n+1); exit end;
if m=0
then s[top,2]:=n+1
else if n=0
then begin top:=top+1; s[top,1]:=m-1; s[top,2]:=1 end
else begin top:=top+1; s[top,1]:=m-1;
top:=top+1; s[top,1]:=m; s[top,2]:=n-1 end
end
end.
下面,我们就以ack(2,1)为例,开始分析递归调用树,采用一个栈记忆每次递归调用时的实参值,每个结点两个域{vm, vn}。对以上实例,递归树以及栈的变化如下: