讲义一 递归的消除

合集下载

递归算法详解

递归算法详解

递归冯文科一、递归的基本概念。

一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。

在程序设计中,函数直接或间接调用自己,就被称为递归调用。

二、递归的最简单应用:通过各项关系及初值求数列的某一项。

在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及a与前面临n近几项之间的关系。

要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。

比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:如果需要写一个函数来求a的值,那么可以很容易地写成这样:n这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。

递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。

以上面求阶乘数列的函数)f为例。

如在求)3(f时,由于3不是特殊值,因(n此需要计算)2(*3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计算)1(2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直*接得出1)2(=2f,再返回上一步,得=f)1(*)1(=f,返回上一步,得23)3(=f,从而得最终解。

=f=*6)2(23*用图解来说明,就是下面再看一个稍复杂点的例子。

【例1】数列}{n a 的前几项为1、111+、11111++、1111111+++、……输入n ,编程求n a 的精确分数解。

分析:这个题目较易,发现11=a ,其它情况下有111-+=n n a a 。

原题:解决一个递归关系式。

原题:解决一个递归关系式。

原题:解决一个递归关系式。

原题:解决一个递归关系式递归关系式是一种数学函数或算法,它通过将问题分解为更小的子问题来定义自身。

解决递归关系式的方法是通过找到基本情况和递归条件来递归地计算结果。

以下是解决一个递归关系式的一般步骤:1. 定义递归函数:首先要定义一个递归函数,用于解决目标问题。

递归函数应该接受一个或多个参数,并返回一个值。

2. 确定基本情况:基本情况是指递归函数的终止条件。

在递归过程中,当满足基本情况时,递归将停止并返回一个特定的值。

3. 拆分问题:在递归函数中,将问题拆分为一个或多个更小的子问题。

这些子问题应该比原始问题更简单。

4. 调用递归函数:在递归函数中,调用自身来解决更小的子问题。

通过递归调用,逐步解决子问题直到达到基本情况。

5. 结合子问题结果:将子问题的结果合并起来,得到原始问题的解。

下面是一个简单的例子来解释如何解决递归关系式:假设我们要计算斐波那契数列中第n个数的值。

def fibonacci(n):if n <= 1:return nelse:return fibonacci(n-1) + fibonacci(n-2)result = fibonacci(5)print(result) # 输出:5在这个例子中,我们定义了一个递归函数`fibonacci`来计算斐波那契数列。

基本情况是当`n`小于等于1时,直接返回`n`。

当`n`大于1时,递归调用`fibonacci`函数来解决更小的子问题`fibonacci(n-1)`和`fibonacci(n-2)`,然后将它们的结果合并起来。

通过逐步解决子问题,最终我们得到第5个斐波那契数的值为5。

总而言之,解决一个递归关系式的关键是确定基本情况和递归条件,并通过递归调用来解决更小的子问题。

通过合并子问题的结果,最终可以得到原始问题的解。

2014考研计算机数据结构专项精讲课程讲义-第三部分-第三章 栈

2014考研计算机数据结构专项精讲课程讲义-第三部分-第三章 栈
top1--;return(SPACE[top1+1]); //返回出栈元素。 case2:if(top2==N){printf(“栈空\n”);exit(0);}
top2++;return(SPACE[top2-1]); //返回出栈元素。 (2)栈满条件:top2-top1=1 栈空条件:top1=-1 并且 top2=N //top1=-1 为左栈空,top2=N 为右栈空
2. 当两个栈共享一存储区时,栈利用一维数组 stack(1,n)表示,两栈顶指针为 top[1]与 top[2], 则当栈 1 空时,top[1]为_______,栈 2 空时 ,top[2]为_______,栈满时为_______。 0 n+1 top[1]+1=top[2]
3.表达式 23+((12*3-2)/4+34*5/7)+108/9 的后缀表达式是_______。 23.12.3*2-4/34.5*7/++108.9/+(注:表达式中的点(.)表示将数隔开,如 23.12.3 是三个数)
三、填空 1.设有一个空栈,栈顶指针为 1000H(十六进制),现有输入序列为 1,2,3,4,5,经过 PUSH,PUSH,POP,PUSH,POP,PUSH,PUSH 之 后 , 输 出 序 列 是 _______ , 而 栈 顶 指 针 值 是 _______H。设栈为顺序栈,每个元素占 4 个字节。 23 100CH
3 AB
F#
#-
B*C/D-E↑F# PUSH(OPND,B)
4 AB
# - * *C/D-E↑F# PUSH(OPTR,’*’)
5 ABC
# - * C/D-E↑F#

编译原理实验二:消除文法的左递归.doc

编译原理实验二:消除文法的左递归.doc

编译原理实验报告实验名称消除文法的左递归实验时间2013年11月12日院系计算机科学与电子技术系班级11计算机软件学号JV114001 JV114095 JP114065 姓名唐茹韩强强徐思维1.试验目的:输入:任意的上下文无关文法。

输出:消除了左递归的等价文法。

2.实验原理:1.直接左递归的消除消除产生式中的直接左递归是比较容易的。

例如假设非终结符P 的规则为:P →P α / β其中,β是不以P 开头的符号串。

那么,我们可以把P 的规则改写为如下的非直接左递归形式: P →βP ’P ’→αP ’ / ε这两条规则和原来的规则是等价的,即两种形式从P 推出的符号串是相同的。

设有简单表达式文法G[E]: E →E+T/ T T →T*F/ F F →(E )/ I经消除直接左递归后得到如下文法: E →TE ’E ’ →+TE ’/ ε T →FT ’T ’ →*FT ’/ εF →(E )/ I考虑更一般的情况,假定关于非终结符P 的规则为P →P α1 / P α2 /…/ P αn / β1 / β2 /…/βm其中,αi (I =1,2,…,n )都不为ε,而每个βj (j =1,2,…,m )都不以P 开头,将上述规则改写为如下形式即可消除P 的直接左递归:P →β1 P ’ / β2 P ’ /…/βm P ’P ’ →α1P ’ / α2 P ’ /…/ αn P ’ /ε 2.间接左递归的消除直接左递归见诸于表面,利用以上的方法可以很容易将其消除,即把直接左递归改写成直接右递归。

然而文法表面上不存在左递归并不意味着该文法就不存在左递归了。

有些文法虽然表面上不存在左递归,但却隐藏着左递归。

例如,设有文法G[S]:S →Qc/ c Q →Rb/ b R →Sa/ a虽不具有左递归,但S 、Q 、R 都是左递归的,因为经过若干次推导有 S ⇒Qc ⇒Rbc ⇒SabcQ ⇒Rb ⇒Sab ⇒Qcab R ⇒Sa ⇒Qca ⇒Rbca就显现出其左递归性了,这就是间接左递归文法。

递归算法详解

递归算法详解

递归冯文科一、递归的基本概念。

一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。

在程序设计中,函数直接或间接调用自己,就被称为递归调用。

二、递归的最简单应用:通过各项关系及初值求数列的某一项。

在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及a与前面临n近几项之间的关系。

要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。

比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:如果需要写一个函数来求a的值,那么可以很容易地写成这样:n这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。

递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。

以上面求阶乘数列的函数)f为例。

如在求)3(f时,由于3不是特殊值,因(n此需要计算)2(*3f,但)2(f是对它自己的调用,于是再计算)2(f,2也不是特殊值,需要计算)1(2f,需要知道)1(f的值,再计算)1(f,1是特殊值,于是直*接得出1)2(=2f,再返回上一步,得=f)1(*)1(=f,返回上一步,得23)3(=f,从而得最终解。

=f=*6)2(23*用图解来说明,就是下面再看一个稍复杂点的例子。

【例1】数列}{n a 的前几项为1、111+、11111++、1111111+++、……输入n ,编程求n a 的精确分数解。

分析:这个题目较易,发现11=a ,其它情况下有111-+=n n a a 。

消除文法左递归

消除文法左递归

编译原理实验实验名称:消除文法左递归姓名:学号:教师签字:成绩:消除文法的左递归实验目的:实现消除左递归实验要求:输入任意的上下文无关文法,输出消除了左递归的等价文法。

实验原理:1.非终结符的排列为A1,A2……An2.For(i=1;i<=n;i++)For(j=1;j<=i-1;j++)If(Ai→Aj&& A→B1|B2|…|Bn) 替换为Ai=B1r|B2r|…|Bnr;3.消除Ai的左递归4.消除无用产生式消除直接左递归的方法是:将左递归改为右递归。

消除简介左递归的方法是:先通过产生式非终结符置换,将间接左递归变为直接左递归,然后消除直接左递归。

实验代码:#include<iostream>#include<string>using namespace std;//*******************************structwenfa //结构体定义{string left; //产生式左部string right; //产生式右部};//************************************void change(wenfa *p,char *q,intn,int count) //消除左递归{int count1=n;int flag=0;for(int i=0;i <n;i++)if(p[i].left[0]==q[0])if(p[i].left[0]==p[i].right[0])flag++;if(flag!=0){for(int i=0;i <n;i++)if(p[i].left[0]==q[0])if(p[i].left[0]==p[i].right[0]){stringstr;str=p[i].right.substr(1,int (p[i].right.length())); string temp=p[i].left;string temp1="'";p[i].left=temp+temp1;p[i].right=str+p[i].left;}else{string temp=p[i].left;string temp1="'";temp=temp+temp1;p[i].right=p[i].right+temp;}string str="'";p[count1].left=p[0].left[0]+str;p[count1].right="#";}for( i=0;i <= count;i++){for(int j=0;j <i;j++){for(int g=0;g <n;g++)if(q[i]==p[g].left[0])if(p[g].right[0]==q[j]){for(int h=0;h < n*n;h++)if(p[h].left[0]==q[j]&&int (p[h].left.length())==1) {stringstr;str=p[g].right.substr(1,int (p[g].right.length())); p[++count1].left=p[g].left;p[count1].right=p[h].right+str;}p[g].left="";p[g].right="";}}}for( i=0;i <= count;i++){flag=0;for(int j=0;j < n*n;j++)if(p[j].left[0]==q[i])if(p[j].left[0]==p[j].right[0])flag++;if(flag!=0){for(int j=0;j <= n*n;j++)if(p[j].left[0]==q[i])if(p[j].left[0]==p[j].right[0]){stringstr;str=p[j].right.substr(1,int (p[j].right.length()));string temp=p[j].left;string temp1="'";p[j].left=temp+temp1;p[j].right=str+p[j].left;}else{string temp=p[j].left;string temp1="'";temp=temp+temp1;p[j].right=p[j].right+temp;}string str="'";p[++count1].left=q[i]+str;p[count1].right="#";}}}//******************************************************int Delete(wenfa *p,int n){return 0;}//*********************************************************************** int main(){cout<<" ********************消除左递归*********************"<<endl<<endl;inti,j,flag=0,count=1,n;cout<<"输入产生式的数目:";wenfa *p=new wenfa[60];cout<<"请输入文法的个产生式:"<<endl;for(i=0;i<n;i++) //接收产生式{cin>>p[i].left;cout<<"\b-->";cin>>p[i].right;cout<<endl;}cout<<endl;cout<<"输入的文法产生式为:"<<endl;for(i=0;i <n;i++) //输出产生式{cout<<"\t";cout<<p[i].left<<"-->"<<p[i].right<<endl;}cout<<endl<<endl;char q[20];q[0]=p[0].left[0];for(i=1;i<n;i++){flag=0;for(j=0;j<i;j++)if(p[i].left==p[j].left)flag++;if(flag==0)q[count++]=p[i].left[0];}count--;change(p,q,n,count);Delete(p,n);cout<<"消除递归后的产生式为:"<<endl;for(i=0;i <= count;i++){for(int j=0;j <= n*n;j++)if( (p[j].left[0]==q[i]) &&int (p[j].left.length())==1 )cout<<p[j].left<<"---->"<<p[j].right<<endl;else continue;for( j=0;j <= n*n;j++)if( (p[j].left[0]==q[i]) &&int (p[j].left.length())==2 )cout<<p[j].left<<"---->"<<p[j].right<<endl;else continue;return 0;}实验截图:。

消除左递归

消除左递归

直接消除左递归方法:
回溯:分析工作要部分地或全部地退回去重做。

条件:在文法中,对于某个非终结符地规则是其右部有多个选择,并且根据所面临地输入符号不能准确地确定所要地选择时,就可能出现回溯.
回溯带来地问题:
严重地影响了效率,只有在理论地层面上地意义而没有实际地意义。

影响效率地原因:1)语法分析需要重做;2)语义处理工作要倒退重新来做;3)当一个非终结符用某一后选式匹配成功是,这种成功可能仅仅是暂时的;4)当最终报告分析不成功时,难以知道输入串出错地准确位置;5)穷尽试探法,效率低下,代价之高。

假定P关于的全部产生式是
P→Pα1| Pα2|…| Pαm| β 1| β 2 |… | βn
其中,每个α都不等于ε,而每个β都不以P开头,
那么消除P地直接左递归就是把这个规则改写成:
P→ | β 1 P’| β 2 P’|…| β n P’
P’ →α1P’| α2P’| …| αmP’| ε
例4.2消去以下文法的直接左递归。

E→E+T|T
T→T*F|F
F→(E)|i
解:
E →TE´
E ´→+TE´|ε
T →FT´
T´→*FT´|ε
F→(E)|i
练习1]:
已知G[E]:
E→T*F | T/F | F
T →F | T*F | T/F
请消除该文法的左递归。

消除文法左递归

消除文法左递归

学号:专业:姓名:实验日期:2012.4.13 教师签字:成绩:实验名称:实验二:消除文法的左递归实验目的:1. 掌握上下文无关文法类型的定义,及与其他类型文法的区别;2.熟悉上下文无关文法类型的判断,能够快速按照要求写出对应文法类型的文法用例;3.给出一个上下文无关文法类型,能够正确判断其是否存在左递归,若存在则消除直接、间接左递归。

实验原理:1.文法中如果存在左递归,会产生循环递归,故需要将文法中的左递归给删除掉。

2.删除左递归需要删除直接左递归与间接左递归。

3.在删除左递归是,使用循环消元法,将左线性递归修改为又递归。

4.最后删除无用产生式。

实验内容:1.实验要求:输入任意的一个上下文无关文法,判断其是否存在左递归,若存在左递归则输出消除了左递归的等价文法。

2.实验代码:#include <iostream>#include <fstream>#include <string>#include <algorithm>#include <vector>using namespace std;struct relation{string _left;string _right;}; //关系最大为MAXN条vector<relation> rel;string VN;void print(){cout<<"[CFG消除左递归COPYRIGHT FROM NINGYU 2012/4/13]"<<endl; }bool cmp(const relation &r1,const relation &r2){if(r1._left>r2._left) return true;else if(r1._left==r2._left&&r1._right >r2._right) return true;return false;}relation get_realation(string str){ //将一个字符串生成式分为左右部输入格式为->,返回生成式结构体int t=str.find('-');relation r;r._left=str.substr(0,t);r._right=str.substr(t+2,str.length()-t);return r;}bool isUpper(char c){if(c>='A'&&c<='Z') return true;else return false;}vector<relation> find_firstVn(char c_left,char c_right){ //查找所有c为左部的产生式,c_right 为右部产生式vector<relation> v;for(int i=0;i<rel.size();i++){if(rel[i]._left[0]==c_left&&rel[i]._right[0]==c_right){v.push_back(rel[i]);rel.erase(rel.begin()+i);}return v;}vector<relation> find_Vn(char c){ //查找左部为c的产生式vector<relation> v;for(int i=0;i<rel.size();i++)if(rel[i]._left[0]==c){v.push_back(rel[i]);//rel.erase(rel.begin()+i);}return v;}bool is_exist(relation r){int i;for(i=0;i<rel.size();i++){if(r._left==rel[i]._left&&r._right==rel[i]._right)break;}if(i==rel.size()) return true;return false;}void releft(){ //消除一切左递归vector<relation> v1,v2,v3;relation r;for(int i=0;i<VN.length();i++)for (int j=0;j<=i-1;j++){v1=find_firstVn(VN[i],VN[j]);for(int t=0;t<v1.size();t++){r._left=v1[t]._left;r._right=v1[t]._right.substr(1,v1[t]._right.length()-1);if(is_exist(r)) rel.push_back(r);v2=find_Vn(VN[j]);for(int q=0;q<v2.size();q++){r._right=v2[q]._right+v1[t]._right.substr(1,v1[t]._right.length()-1); //得到替换Ai->br;if(is_exist(r)) rel.push_back(r);}}}void init(){cout<<"请输入文法,以Ctrl+Z结束"<<endl;string strPath;relation r;int p;while(cin>>strPath){rel.push_back(get_realation(strPath));//得到左部与右部;if(rel[rel.size()-1]._left.length()==1){ //得到左部的非终结符char c=rel[rel.size()-1]._left[0];if(c<='Z'&&c>='A'){int t=VN.find(c);if(t==string::npos)VN+=c;}}}}vector<relation> find_1(char c){ //查找c的直接左递归vector<relation> v;for(int i=0;i<rel.size();i++)if(rel[i]._left[0]==c&&rel[i]._right[0]==c){v.push_back(rel[i]);rel.erase(rel.begin()+i);}return v;}vector<relation> find_2(char c){//查找c的非左递归vector<relation> v;for(int i=0;i<rel.size();i++)if(rel[i]._left[0]==c&&rel[i]._right[0]!=c&&*(rel[i]._right.end()-1)!='.'){ v.push_back(rel[i]);rel.erase(rel.begin()+i);}return v;}void re_dir_left(int i){//删除有问题生成式!!!char c=VN[i];vector<relation> v1,v2;v1=find_1(c);relation r;string ch;ch.push_back(c);ch.push_back('.');bool p=false;////////////if(v1.size()>=1)p=true;/////////////////if(p){v2=find_2(c);r._left="";r._left.push_back(c);r._right="";r._right.push_back('#');if(is_exist(r)) rel.push_back(r);}for (int q=0;q<v1.size();q++){r._left=v1[q]._left;r._right=v1[q]._right.substr(1,v1[q]._right.length()-1)+ch;if(is_exist(r)) rel.push_back(r);for(int k=0;k<v2.size();k++){r._right=v2[k]._right+ch;if(is_exist(r)) rel.push_back(r);}}}int main(){print();init();sort(rel.begin(),rel.end(),cmp);releft();//消除左递归函数for(int i=0;i<VN.size();i++)re_dir_left(i);cout<<endl<<"消除左递归之后的文法为:"<<endl;sort(rel.begin(),rel.end(),cmp);for(int i=0;i<rel.size();i++)//测试读入关系是否正确cout<<rel[i]._left<<"->"<<rel[i]._right<<endl;print();}3..实验结果:。

递归算法详解

递归算法详解

递归冯文科一、递归的基本概念。

一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。

在程序设计中,函数直接或间接调用自己,就被称为递归调用。

二、递归的最简单应用:通过各项关系及初值求数列的某一项。

在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及n a 与前面临近几项之间的关系。

要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。

比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:⎩⎨⎧>==−1,1,11n na n a n n 如果需要写一个函数来求n a 的值,那么可以很容易地写成这样:int f(int n){if(n==1)return 1;return n*f(n-1);}这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。

递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。

以上面求阶乘数列的函数)(n f 为例。

如在求)3(f 时,由于3不是特殊值,因此需要计算)2(*3f ,但)2(f 是对它自己的调用,于是再计算)2(f ,2也不是特殊值,需要计算)1(*2f ,需要知道)1(f 的值,再计算)1(f ,1是特殊值,于是直接得出1)1(=f ,返回上一步,得2)1(*2)2(==f f ,再返回上一步,得62*3)2(*3)3(===f f ,从而得最终解。

编译原理实验二:消除文法的左递归说课讲解

编译原理实验二:消除文法的左递归说课讲解

编译原理实验报告实验名称消除文法的左递归实验时间2013年11月12日院系计算机科学与电子技术系班级11计算机软件学号JV114001 JV114095 JP114065 姓名唐茹韩强强徐思维1.试验目的:输入:任意的上下文无关文法。

输出:消除了左递归的等价文法。

2.实验原理:1.直接左递归的消除消除产生式中的直接左递归是比较容易的。

例如假设非终结符P 的规则为:P →P α / β其中,β是不以P 开头的符号串。

那么,我们可以把P 的规则改写为如下的非直接左递归形式: P →βP ’P ’→αP ’ / ε这两条规则和原来的规则是等价的,即两种形式从P 推出的符号串是相同的。

设有简单表达式文法G[E]: E →E+T/ T T →T*F/ F F →(E )/ I经消除直接左递归后得到如下文法: E →TE ’E ’ →+TE ’/ ε T →FT ’T ’ →*FT ’/ εF →(E )/ I考虑更一般的情况,假定关于非终结符P 的规则为P →P α1 / P α2 /…/ P αn / β1 / β2 /…/βm其中,αi (I =1,2,…,n )都不为ε,而每个βj (j =1,2,…,m )都不以P 开头,将上述规则改写为如下形式即可消除P 的直接左递归:P →β1 P ’ / β2 P ’ /…/βm P ’P ’ →α1P ’ / α2 P ’ /…/ αn P ’ /ε 2.间接左递归的消除直接左递归见诸于表面,利用以上的方法可以很容易将其消除,即把直接左递归改写成直接右递归。

然而文法表面上不存在左递归并不意味着该文法就不存在左递归了。

有些文法虽然表面上不存在左递归,但却隐藏着左递归。

例如,设有文法G[S]:S →Qc/ c Q →Rb/ b R →Sa/ a虽不具有左递归,但S 、Q 、R 都是左递归的,因为经过若干次推导有 S ⇒Qc ⇒Rbc ⇒SabcQ ⇒Rb ⇒Sab ⇒Qcab R ⇒Sa ⇒Qca ⇒Rbca就显现出其左递归性了,这就是间接左递归文法。

递归算法详解

递归算法详解

递 归冯文科一、递归的基本概念。

一个函数、概念或数学结构,如果在其定义或说明内部直接或间接地出现对其本身的引用,或者是为了描述问题的某一状态,必须要用至它的上一状态,而描述上一状态,又必须用到它的上一状态……这种用自己来定义自己的方法,称之为递归或递归定义。

在程序设计中,函数直接或间接调用自己,就被称为递归调用。

二、递归的最简单应用:通过各项关系及初值求数列的某一项。

在数学中,有这样一种数列,很难求出它的通项公式,但数列中各项间关系却很简单,于是人们想出另一种办法来描述这种数列:通过初值及n a 与前面临近几项之间的关系。

要使用这样的描述方式,至少要提供两个信息:一是最前面几项的数值,一是数列间各项的关系。

比如阶乘数列1、2、6、24、120、720……如果用上面的方式来描述它,应该是:如果需要写一个函数来求n a 的值,那么可以很容易地写成这样: int f(int n){if(n==1)return 1;return n*f(n-1);}这就是递归函数的最简单形式,从中可以明显看出递归函数都有的一个特点:先处理一些特殊情况——这也是递归函数的第一个出口,再处理递归关系——这形成递归函数的第二个出口。

递归函数的执行过程总是先通过递归关系不断地缩小问题的规模,直到简单到可以作为特殊情况处理而得出直接的结果,再通过递归关系逐层返回到原来的数据规模,最终得出问题的解。

以上面求阶乘数列的函数)(n f 为例。

如在求)3(f 时,由于3不是特殊值,因此需要计算)2(*3f ,但)2(f 是对它自己的调用,于是再计算)2(f ,2也不是特殊值,需要计算)1(*2f ,需要知道)1(f 的值,再计算)1(f ,1是特殊值,于是直接得出1)1(=f ,返回上一步,得2)1(*2)2(==f f ,再返回上一步,得62*3)2(*3)3(===f f ,从而得最终解。

用图解来说明,就是下面再看一个稍复杂点的例子。

算法:五步教你消除递归

算法:五步教你消除递归

算法:五步教你消除递归背景递归对于分析问题⽐较有优势,但是基于递归的实现效率就不⾼了,⽽且因为函数栈⼤⼩的限制,递归的层次也有限制。

本⽂给出⼀种通⽤的消除递归的步骤,这样您可以在分析阶段采⽤递归思想,⽽实现阶段采⽤⾮递归算法。

函数的调⽤过程函数的调⽤是基于栈,每次调⽤都涉及如下操作:调⽤开始时:将返回地址和局部变量⼊栈。

调⽤结束时:出栈并将返回到⼊栈时的返回地址。

使⽤堆中分配的栈消除递归递归版本代码⾮递归版本代码消除过程第⼀步:识别递归版本中的代码地址第⼀个代表:原始⽅法调⽤。

倒数第⼀个代表:原始⽅法调⽤结束。

第⼆个代表:⽅法调⽤⼊⼝。

倒数第⼆个代表:⽅法调⽤出⼝。

递归版本中的每个递归调⽤定义⼀个代码地址。

假如递归调⽤了 n 次,则代码地址为:n + 4。

第⼆步:定义栈帧栈帧代表了代码执⾏的上下⽂,将递归版本代码体中⽤到的局部值类型变量定义为栈帧的成员变量,为啥引⽤类型不⽤我就不多说了,另外还需要定义⼀个返回地址成员变量。

第三步:while 循环在 while 循环之前声明⼀个 stack、⼀个 currentReturnValue 和 currentAddress。

第四步:switch 语句。

第五步:填充 case 代码体。

将递归版本的代码做如下变换:函数调⽤使⽤:stack.push(new StackFrame{...}); 和 currentAddress = 2; 。

引⽤的局部变量变为,⽐如:n,变为:stack.Peek().n 。

return 语句变为:currentReturnValue = 1; 和 currentAddress = 4; 。

倒数第⼀个 case 代码体为:return currentReturnValue; 。

最终的效果就是上⾯的⽰例。

汉诺塔练习这个练习是我之前采⽤的⽅式看,思想和上⾯的⾮常相似。

备注搞企业应⽤的应该⽤不到这种消除递归的算法,不过学完以后对递归的理解也更清晰了。

在递归算法中消除递归调用

在递归算法中消除递归调用
这条特征是应用分治法的前提它也是大多数问题可以满足的此特征反映了递归思想的应用能否利用分治法完全取决于问题是否具有这条特征如果具备了前两条特征而不具备第三条特征则可以考虑贪心算法或动态规划
第四讲 递归和分治策略
通过例子理解递归的概念;
掌握设计有效算法的分治策略;
通过几个范例学习分治策略设计技巧;
算法总体思想
对这k个子问题分别求解。如果子问题的规模仍然不够 将小要,求则解再的划较分大为规k个模子的问问题题,分如割此成递k归个的更进小行规下模去的,子直问
题到。问题规模足够小,很容易求出其解为止。
=
n
T(n)
T(n/2)
T(n/2)
2
T(n/2)
T(n/2)
University of Science and Technology of China
边界条件 递归方程
边界条件与递归方程是递归函数的二个要素,递归函 数只有具备了这两个要素,才能在有限次计算后得出 结果。
6
University of Science and Technology of China
递归的例子
例2 排列问题 设计一个递归算法生成n个元素{r1,r2,…,rn}的全排列。 设R={r1,r2,…,rn}是要进行排列的n个元素,Ri=R-{ri}。 集合X中元素的全排列记为perm(X)。 (ri)perm(X)表示在全排列perm(X)的每一个排列前加上前 缀得到的排列。R的全排列可归纳定义如下:
递归的概念
直接或间接地调用自身的算法称为递归算法。用函数 自身给出定义的函数称为递归函数。
由分治法产生的子问题往往是原问题的较小模式,这 就为使用递归技术提供了方便。在这种情况下,反复 应用分治手段,可以使子问题与原问题类型一致而其 规模却不断缩小,最终使子问题缩小到很容易直接求 出其解。这自然导致递归过程的产生。

消除文法左递归的算法

消除文法左递归的算法

消除⽂法左递归的算法存储⽂法的数据结构1 typedef struct P{2char key; // 产⽣式左部3char * value [16]; // 产⽣式右部4int count; // ⼏组规则5 }P;6 typedef struct G{7char * vn ; // ⾮终结符集合8char * vt ; // 终结符集合9 P p[16]; // 产⽣式10char start; // 开始符号11int pcount ;12 }G;⽂法G由多条产⽣式组成,出现在产⽣式左部的⾮终结符,会指向⼀个P⽂法数组,每⼀个数组元素对应⼀个程式的右部,这样的结构显然是对⽂法进⾏了压缩的算法过程1、扫描⽂法,先将间接做递归转换成直接左递归2、借助如下公式,消除直接左递归对形如这样的程式A->Aα1|Aα2|Aα3| Aαn|..|ρ1|ρ2|….| ρn转换成如下形式A->ρ1A'|ρ2A'|ρ3A'A'->α1A'|α2A'|....|ε输⼊1322 S Qc|c32 Q Rb|b42 R Sa|a完整算法1 #include <stdio.h>2 #include <malloc.h>3 #include <string.h>45 typedef struct P{6char key; // 产⽣式左部7char * value [16]; // 产⽣式右部8int count; // ⼏组规则9 }P;10 typedef struct G{11char * vn ; // ⾮终结符集合12char * vt ; // 终结符集合13 P p[16]; // 产⽣式14char start; // 开始符号15int pcount ;16 }G;1718int main(){19int i,n;20 freopen("xczdg.in","r",stdin);21 printf("Plese input P count :");22 scanf("%d",&n);23 printf("\n");24 G g;25 g.pcount = n;26//g.p = (P *)malloc(sizeof(P)*n);27for(i=0;i<n;i++){28 scanf("%d%*c",&g.p[i].count);29 g.p[i].key = getchar();30 getchar();31char ch,str[255];32int sp = 0,c=0;33while((ch = getchar()) != '\n'){34if('|' == ch){35 str[sp]='\0';36 g.p[i].value[c] = (char *) malloc(sizeof(char)*255);37 strcpy(g.p[i].value[c],str);38 sp = 0;39 c++;40 }41else{42 str[sp] = ch;43 sp++;44 }45 }46 str[sp]='\0';47 g.p[i].value[c] = (char *) malloc(sizeof(char)*255);48 strcpy(g.p[i].value[c],str);4950 printf("%c=>%s|%s\n",g.p[i].key,g.p[i].value[0],g.p[i].value[1]);51 }5253for(i=0;i<n;i++){54int j = 0;55for(;j<i;j++){56// 将间接左递归转换成直接左递归57// 扫描Ai的开始符号,⼀定要是⾮终结符58int k;59for(k=0;k<g.p[i].count;k++){60char i_start = g.p[i].value[k][0];61//printf("%c\n",start);62if(i_start==g.p[j].key){63// 满⾜ Ai->Ajr64char tmp[255];65char fiel[255];66 strcpy(fiel,&g.p[i].value[k][1]);6768 strcpy(tmp,g.p[j].value[0]);69 strcpy(g.p[i].value[k],strcat(tmp,fiel));70 printf("%d %s\n",k,g.p[i].value[k]);71int m;72for(m=1;m<g.p[j].count;m++){73 strcpy(tmp,g.p[j].value[m]);74 g.p[i].value[g.p[i].count] = (char *) malloc(sizeof(char)*255);75 strcpy(g.p[i].value[g.p[i].count],strcat(tmp,fiel));76 printf("%d %s\n",g.p[i].count,g.p[i].value[g.p[i].count]);77 g.p[i].count++;7879 }80 }8182 }83 }84// 消除直接左递归85// 扫描Pi.key 为产⽣式右部的所有产⽣式86for(j=0;j<g.p[i].count;j++){87char * pivj = g.p[i].value[j];88if(g.p[i].key == pivj[0]){89// 存在直接左递归90int m;91for(m=0;m<g.p[i].count;m++){92if(m!=j){93// A->ρ1A'|ρ2A'|ρ3A' ρρσσαα94char aci[2] = {g.p[i].key-32,'\0'};95 strcat(g.p[i].value[m],aci); // 这⾥A'直接已A的ASCII码值减32表⽰96 }else{97// A'->α1A'|α2A'|....|ε98 g.p[g.pcount].key = g.p[i].key-32;99 g.p[g.pcount].value[0] = (char *) malloc(sizeof(char)*255);100 strcpy(g.p[g.pcount].value[0],&pivj[1]);101char aci[2] = {g.p[g.pcount].key,'\0'};102 strcat(g.p[g.pcount].value[0],aci);103 g.p[g.pcount].value[1] = (char *) malloc(sizeof(char)*255);104 strcpy(g.p[g.pcount].value[1],"kong");105 g.p[g.pcount].count = 2;106 g.p[i].value[j] = NULL;107 g.pcount++;108 }109 }110break;111 }112 }113114 }115116 printf("\n-----------------------\n");117// 打印⽂法118for(i=0;i<g.pcount;i++){119if(g.p[i].key){120if(g.p[i].key) printf("%c=>",g.p[i].key);121int j;122for(j=0;j<g.p[i].count;j++){123if(g.p[i].value[j]) printf("%s ",g.p[i].value[j]);124 }125 printf("\n");126 }127 }128free(g.p);129return0;130 }运⾏结果(这⾥⽤2代替R',⽤kong代表空字符)。

用面向对象技术消除递归

用面向对象技术消除递归

用面向对象技术消除递归
杨海娟;任小康
【期刊名称】《电脑知识与技术》
【年(卷),期】2006(000)012
【摘要】程序设计中递归的运用可以使编程效率提高,但对时间要求及严的软件而言,递归带来的问题也很多,在网络几何画板软件开发中提出了一种运用面向对象技术消除递归的方法,使得软件代码大幅度缩短,结构清晰,软件的运行速度提高,瓶颈问题得以解决,论证了消除递归的重要性及面向对象技术在系统建模中的强大优势.【总页数】2页(P134,160)
【作者】杨海娟;任小康
【作者单位】西北师范大学数信学院,甘肃,兰州,730020;兰州教育学院计算机系,甘肃,兰州,730020;西北师范大学数信学院,甘肃,兰州,730020:
【正文语种】中文
【中图分类】TP311
【相关文献】
1.递归算法设计与递归消除技术 [J], 刘建芬
2.基于支持向量机递归特征消除的电缆局部放电特征寻优 [J], 李程; 李强; 张启超; 刘子瑞; 李伟
3.基于递归特征消除-加权k近邻算法的多联机系统制冷剂充注量故障诊断策略 [J], 王誉舟; 李正飞; 魏文天; 陈焕新; 程亚豪; 刘倩; 张鉴心
4.基于递归特征消除和Stacking集成学习的股票预测实证研究 [J], 黄秋丽;黄柱
兴;杨燕
5.基于极限树特征递归消除和LightGBM的异常检测模型 [J], 何红艳;黄国言;张炳;贾大苗
因版权原因,仅展示原文概要,查看原文内容请购买。

一种消除递归的新方法 - 页面没有找到

一种消除递归的新方法 - 页面没有找到
基金项 目: 湖南 省教育 厅项 目( 编号: 0 4 09 9 )
图 】 无返 回值递 归函数 非递归化 后的通 用形式
作者简介 陈燕晖, 硕上生, 讲师, 主要研究方向为操作系统C 邢品, 硕十研究生, 主要研究方向高性能计算机系统 罗宇, 教授,主要研 究方 向为操
作 系统
语 句块 3 ; /对 象 3的 a c t i o n } }
下面讨论如何实现具体的类 根据函 数f 对自 身进行调用
出现的位置 我们先讨论两者最简单 形式 的处理 ( 1 ) 简单的顺序语句块; ( 2 ) 简单的选择循环 然后针对最通用 的情况 , 即前两种情况 的组合嵌套 , 给出 了一组规则 2 . 1 . 1 顺序语句块情S t 为简化 讨论假定对f的递归调用 只出现一次 , 即便 出现 多 次处理方法并不需要做任何改变。函数f通用形式为:
对象 i a i t 0 6 j 的a c t i o n操作 , 则该操作实 由对象 1 - 3 的a c t i o n操 作组合完成 ,所 以可以使用图 1 中的栈 s t 把这些对象人栈 然
规则 1 对函 数了 进行 调用的语句 且不 被任何选择/ 循环 语
句包含应该看成一个单独的语 句块 规则 2包 含对函数f进行 调用的选择/ 循环语 句且该 选 择/ 循环 语句 不被 任何选 择/ 循环 语句 包含 ( 即它是 最外 层 的) 应 该看成 一个单独 的语5 J . 块 。该 语句块的 a c t i o n处理 见
v i n u a l v o i d a c t i o n ( ) = 0 ;

该方法把函数f的执行过程抽象成不同 对象的a c t i o n 执
行过程 , 各个对象按次序出现在栈顶。
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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}。

对以上实例,递归树以及栈的变化如下:
相应算法如下
#include
#include
using namespace std;
typedef struct node_t {
unsigned int vm, vn;
}node, *pnode;
unsigned akm ( unsigned int m, unsigned int n ) { std::stack st;
pnode w, w1;
unsigned int v;
unsigned int vt;
//根节点进栈
w = (node *) malloc (sizeof (node));
w->vm = m;
w->vn = n;
st.push (w);
do {
//计算akm(m-1, akm(m, n-1))
while ( st.top( )->vm > 0 ) {
vt = w->vn;
//计算akm(m, n-1), 直到akm(m,0)
while ( st.top()->vn > 0 )
{
w1 = (node *) malloc (sizeof (node));
vt --;
w1->vn = vt;
w1->vm = w->vm;
st.push( w1 );
}
//把akm(m, 0)转换为akm(m-1, 1),并计算
w = st.top( );
st.pop( );
w->vm--;
w->vn = 1;
st.push( w );
vt = w->vn;
}
//计算akm( 0, akm( 1, * ) )
w = st.top();
st.pop( );
w->vn++;
//计算v = akm( 1, * )+1
v = w->vn;
//如果栈不为空,改栈顶为( m-1, v )
if ( !st.empty( ) )
{
w = st.top();
st.pop( );
w->vm--;
w->vn = v;
st.push( w );
}
} while ( !st.empty( ) );
return v;
}
int main()
{
unsigned int rtn;
rtn = akm(3,2);
std::cout << rtn << std::endl; return 0;
}。

相关文档
最新文档