由BNF解释如何用递归表示循环
正巧老夫最近在做C99语法分析的工作,看了<华丽的递归...>的帖子,里面用几个简单的双向递归模拟循环,突然有点儿心得,贴出来分享一下,有错误的地方还请各位看官指正。
先看看3种不同的BNF的产生式,由这三个产生式可以回答二个问题
1 什么样的递归可以用不带栈的循环表示 ?
2 怎么用递归表示循环 ?
1 2 3A : Aa A : aA A : aAb | a | a | c
式1是左递归, 可以产生诸如 a, aa, aaa, aaaa...这样的表达式。说白了,可以用一个简单的"循环" a+ 来表示;式2是尾递归, 也同样产生a, aa, aaa, aaaa...这样的表达式,当然也可以用一个"循环" a+ 来表示。式1和式2都能产生同样的表达式,且都能用循环表示(不同点在于结合性,一个自左向右,一个自右向左,但这不是本文的重点)
下面看看式3,这东东的递归不在头也不在尾而是在中间,可以产生诸如acb, aacbb, aaacbbb这样表达式,初看上去好像也能用某种"循环"来表示,比如a*cb*。但问题是acb,aacbb,aaacbbb中都有一个"特点",c左边的a要和c右边b"数量一致"。这里的"数量一致"可以换成其他说法,比如"匹配"。你把a换成左括号"(",把b换成右括号")" ,aacbb就变成了((c)),这个"数量一致"就变成了"括号匹配"。显然对于式3,你必须要给它一个栈或者其他数据结构去记住c左边到底遇到多少个a,所以仅仅用"循环"是没有办法模拟这种具有"嵌套"的递归结构。
那么到此为止,第一个问题实际上就清楚了。
1 什么样的递归可以用不带栈的循环表示 ?
一般左递归和尾递归可以很顺利的转换为一个简单的循环。
但是在命令式的语言中(函数式的老夫尚不涉及,不敢胡说),左递归对应的代码片段是有问题,一进入A()就立刻调用A(),实际上死循环了,所以左递归一般比较少存在。那么就只剩下尾递归了。由于尾递归可以很容易的变成循环结构(有些地方编译器都可以自己给优化),因此第二个问题也清楚了,"
void A(){ A(); a();}
2 怎么用递归表示循环 ?
可以用尾递归表示循环
当然一般情况下,我们没有必要干这个傻事儿,典型的吃饱了撑的。但是从理解问题本质出发,如果能多用递归的思想解决问题,确实蛮有意思的。依稀记得某大牛曾有句话 "To iterate is human,to recurse divine"。随着工作年限的增加,确实觉得这句话越来越有一定道理了。
还是以"正整数分解为数目最少的平方数之和"为例,首先用循环的思想,最朴素的是先看看正整数M能不能分解为1个平方数之和,能不能分解成2个,3个,4个......当然越小越好。
大概是
void compute(int n){ for (int i = 1; i < n; i++) //do something for i 能不能分解成1个,2个,3个.... }
现在要把它变成递归形式,显然是函数compute每调用一次,就相当于以前循环一次。for语句里有2个变量控制循环,一个是i,一个是n, 在"递归"的方式中,它们应该是函数的参数,在函数间传递,模拟以前的循环间传递。于是compute的接口就增加了2个参数,如果compute还有其他形参,可以原封不动的放在前面。
这里有个巧合,前面的n和for循环里的n其实是一个,所以3个参数就变成了2个参数。
void compute( int n, int i)
循环的还有2个重要结构,一个是终止条件,一个是递增步长,再加上上面说的2个控制变量,可以按下面的模板进行转换
void compute( int n, int i) // 增加2个形参,表示控制循环的变量{// 循环终止条件 for语句 i < n 部分 // do something for i 这部分原封不动 // 通过函数调用表示步长递增 for语句 i++部分}
最终
void compute( int n, int i) // 增加2个形参,表示控制循环的变量{ if (i >= n) return; // for语句 i < n 部分 // do something for i // 这部分依然原封不动 compute(n, i + 1); // for语句 i++部分}
整个过程比较机械化,可以不怎么动脑子,把 do something for i 合进来后,修正下函数返回值。
// n能否为m个平方数之和int compute(int n, int m){ if (m >= n) return m; //n 能否为 m个平方数 之和 return isSquareSum(n, m, n, 1) ? m : compute(n, m + 1);}
isSquareSum也是用递归来模拟循环,实际上isSquareSum最开始只有前2个参数,后面2个同compute一样,也是用来模拟循环的,实际上,看到这种模式可以先别管后面的形参,不然很容易搞糊涂。
原始的isSquareSum 是这样的
bool isSquareSum(int n, int m){ //是否完全平方数 if (m == 1) return isSqaure(n); // something bool _result = false; // something for (int i = 1; i * i < n; i ++) { _result = isSquareSum(n - i * i, m - 1); // something if (_result) return _result; // something } return _result;}
isSquareSum本身已经是一个递归了,将"n能否为m个平方数之和" 递归表示为" n - 一个平方数 能否为 m - 1个平方数之和","n - 一个平方数"
再次用for 循环来穷举,1,4,9,16.... 这个循环还可以再次用上面的模板来改造成递归,先增加2个控制参数v,k,然后写终止条件,然后写调用函数。
bool isSquareSum(int n, int m, int v, int i) //增加控制参数{ if ( i * i >= v) return false; // 终止条件 return isSquareSum(n, m, v, i + 1); // 递增}
然后再把something部分给搬过来
bool isSquareSum(int n, int m, int v, int i){ if (m == 1) return isSqaure(n); if ( i * i >= v) return false; return isSquareSum(n - i * i, m - 1, v , i) ? true : isSquareSum(n, m, v, i + 1);}
这样也就形成了键盘农夫所说的双向递归,左边的递归前面的参数,右边的递归后面的参数。最后是完整的代码,isSqaure部分是偷大懒了,老夫也是一时心血来潮,毕竟不是专门做算法的。话说老夫今日虚火,昨个三黄片吃多了,折腾了大半宿,今天此番活动活动脑筋,竟然精神了很多,奇哉,奇哉啊
int data[17] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; bool isSqaure(int i){ bool result[18] = { false, true, false, false, true, false, false, false, false, true, false,false, false, false,false, false, true, false}; return result[i];} //n 能否为 m个平方数 之和bool isSquareSum(int n, int m, int v, int i){ if (m == 1) return isSqaure(n); if ( i * i >= v) return false; return isSquareSum(n - i * i, m - 1, v , i) ? true : isSquareSum(n, m, v, i + 1);} // 正整数分解为数目最少的平方数之和// n 正整数// m 起始平方数个数//int compute(int n, int m){ if (m >= n) return m; return isSquareSum(n, m, n, 1) ? m : compute(n, m + 1);} int leastSquareSum(int n){ return compute(n, 1);} int _tmain(int argc, _TCHAR* argv[]){ int len = sizeof(data) / sizeof(int); for(int i = 0; i < len; i++) printf("%d least square sum is %d\n", data[i], leastSquareSum( data[i])); system("pause");}
Powered by Zoundry Raven
转载于:https://www.cnblogs.com/quixotic/archive/2011/12/05/2276836.html
由BNF解释如何用递归表示循环相关推荐
- [python]练习之递归和循环实现斐波拉契数列
1 # 程序功能:用递归和循环实现斐波拉契数列 2 # 0 1 1 2 3 5 8 13 21 34 3 4 def digui_fibo(number): 5 if number == 1: 6 r ...
- python缩进的用途和使用方法_如何用Python减少循环层次和缩进的技巧
本文实例分析了Python减少循环层次和缩进的技巧.分享给大家供大家参考,具体如下: 我们知道Python中冒号和缩进代表大括号,这样写已经可以节省很多代码行数,但是可以更优化,尽可能减少循环的层次和 ...
- MoeCTF 2021Re部分------ez(递归转循环)
文章目录 ida 分析 总结 ida for ( i = 0; i <= 75; ++i ){Character = fuck(i * i) ^ flag[i];putchar(Characte ...
- 递归的效率问题及递归与循环比较
1.所谓的递归慢到底是什么原因呢? 大家都知道递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的.具体是每次调用函数本身要保存的内容包括:局 ...
- 一层循环时间复杂度_数据结构:二叉排序树的前/中/后序遍历(递归与循环两种版本)...
树的设计初衷与操作时间复杂度 树这种数据结构的出现主要是对链表数据结构的优化,链表数据结构是线性结构,操作一般需要O(N)的时间复杂度,树是链表的变形,即链表的每个节点包含一个节点,而树的节点可以包含 ...
- 二分查找法(递归与循环实现)
问题: 给定一个排序数组和一个数k,要求找到第一个k的位置和最后一个k的位置 解析: 由于给定的数组是从小到大排序的,故可以按照二分查找法来找,下面分别从递归和循环两种方法来阐述: //递归方法 in ...
- 数据结构与算法--再谈递归与循环(斐波那契数列)
再谈递归与循环 在某些算法中,可能需要重复计算相同的问题,通常我们可以选择用递归或者循环两种方法.递归是一个函数内部的调用这个函数自身.循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算. ...
- python中递归函数的基例_详谈Python基础之内置函数和递归 Python递归和循环的区别...
Python 递归函数基例 2. 关于递归函数基例的说明,以下选项中错误的是 A 递归函数的基例决定所谓基例就是不需要递归就能求解的,一般来说是问题的最小规模下的解. 例如:斐波那契数列递归,f(n) ...
- php+求二分查找递归算法,PHP二分查找(递归和循环)
二分查找可以通过递归和循环来实现, 思路如下: 将要查找的数和中间数进行比较, 如果相等,则表示找到,返回下标 如果要查找的数小于中间这个数,则说明要查找的数分布在数组左边,修改right边界,使其等 ...
- 递归和循环两种方法完成树的镜像转换
/* copyright@nciaebupt 转载出处:http://blog.csdn.net/nciaebupt/article/details/8506038 题目:输入一颗二元查找树,将该树转 ...
最新文章
- 如何设计一个高可用系统?要考虑哪些地方?
- MySQL group replication
- 利用ArcGIS Engine、VS .NET和Windows控件开发GIS应用
- DES加密解密算法(前端后端)
- 服务器centos安装mysql_centos下安装mysql服务器的方法
- autojs命令代码大全_各个主流品牌手机的命令代码大全,安卓工程模式的指令大全!...
- 如何在同一地方组建多个 ZigBee 网络
- Mysql 使用sql语句快速复制表和数据
- SpringBoot之实例程序
- 使用rsync无密码传输
- 221. Maximal Square
- 网络分析软件(科来网络分析软件)
- 面试官如何对应聘者的素质与能力做出相对准确的判断
- 计算机本地网络如何共享,本地网络共享如何实现?
- 《碎玉投珠》的读后感想法心得范文3800字
- 类对象初始化和Initializer_list的
- 微信公众平台简易设计使用
- 苹果手机2019年什么时候出新款_[创立24周年]焕新,不换我初心 因强大而简单.智者,驭时而进 - 手机数码电玩维修...
- 虚拟机安装安装增强失败:modprobe vboxguest failed
- MFC提示this application has requested the runtime to terminate it in an unusual way editbox框已经删了还在使用