正巧老夫最近在做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解释如何用递归表示循环相关推荐

  1. [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 ...

  2. python缩进的用途和使用方法_如何用Python减少循环层次和缩进的技巧

    本文实例分析了Python减少循环层次和缩进的技巧.分享给大家供大家参考,具体如下: 我们知道Python中冒号和缩进代表大括号,这样写已经可以节省很多代码行数,但是可以更优化,尽可能减少循环的层次和 ...

  3. MoeCTF 2021Re部分------ez(递归转循环)

    文章目录 ida 分析 总结 ida for ( i = 0; i <= 75; ++i ){Character = fuck(i * i) ^ flag[i];putchar(Characte ...

  4. 递归的效率问题及递归与循环比较

    1.所谓的递归慢到底是什么原因呢? 大家都知道递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的.具体是每次调用函数本身要保存的内容包括:局 ...

  5. 一层循环时间复杂度_数据结构:二叉排序树的前/中/后序遍历(递归与循环两种版本)...

    树的设计初衷与操作时间复杂度 树这种数据结构的出现主要是对链表数据结构的优化,链表数据结构是线性结构,操作一般需要O(N)的时间复杂度,树是链表的变形,即链表的每个节点包含一个节点,而树的节点可以包含 ...

  6. 二分查找法(递归与循环实现)

    问题: 给定一个排序数组和一个数k,要求找到第一个k的位置和最后一个k的位置 解析: 由于给定的数组是从小到大排序的,故可以按照二分查找法来找,下面分别从递归和循环两种方法来阐述: //递归方法 in ...

  7. 数据结构与算法--再谈递归与循环(斐波那契数列)

    再谈递归与循环 在某些算法中,可能需要重复计算相同的问题,通常我们可以选择用递归或者循环两种方法.递归是一个函数内部的调用这个函数自身.循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算. ...

  8. python中递归函数的基例_详谈Python基础之内置函数和递归 Python递归和循环的区别...

    Python 递归函数基例 2. 关于递归函数基例的说明,以下选项中错误的是 A 递归函数的基例决定所谓基例就是不需要递归就能求解的,一般来说是问题的最小规模下的解. 例如:斐波那契数列递归,f(n) ...

  9. php+求二分查找递归算法,PHP二分查找(递归和循环)

    二分查找可以通过递归和循环来实现, 思路如下: 将要查找的数和中间数进行比较, 如果相等,则表示找到,返回下标 如果要查找的数小于中间这个数,则说明要查找的数分布在数组左边,修改right边界,使其等 ...

  10. 递归和循环两种方法完成树的镜像转换

    /* copyright@nciaebupt 转载出处:http://blog.csdn.net/nciaebupt/article/details/8506038 题目:输入一颗二元查找树,将该树转 ...

最新文章

  1. 如何设计一个高可用系统?要考虑哪些地方?
  2. MySQL group replication
  3. 利用ArcGIS Engine、VS .NET和Windows控件开发GIS应用
  4. DES加密解密算法(前端后端)
  5. 服务器centos安装mysql_centos下安装mysql服务器的方法
  6. autojs命令代码大全_各个主流品牌手机的命令代码大全,安卓工程模式的指令大全!...
  7. 如何在同一地方组建多个 ZigBee 网络
  8. Mysql 使用sql语句快速复制表和数据
  9. SpringBoot之实例程序
  10. 使用rsync无密码传输
  11. 221. Maximal Square
  12. 网络分析软件(科来网络分析软件)
  13. 面试官如何对应聘者的素质与能力做出相对准确的判断
  14. 计算机本地网络如何共享,本地网络共享如何实现?
  15. 《碎玉投珠》的读后感想法心得范文3800字
  16. 类对象初始化和Initializer_list的
  17. 微信公众平台简易设计使用
  18. 苹果手机2019年什么时候出新款_[创立24周年]焕新,不换我初心 因强大而简单.智者,驭时而进 - 手机数码电玩维修...
  19. 虚拟机安装安装增强失败:modprobe vboxguest failed
  20. MFC提示this application has requested the runtime to terminate it in an unusual way editbox框已经删了还在使用

热门文章

  1. android 上传图片给js,安卓中HTML5图片上传实例详解
  2. shell基础之更改IP
  3. Django 入门初探
  4. [总结] LCT学习笔记
  5. 部署java项目到阿里云服务器(centos7版本)
  6. JDBCUtils——DBCP
  7. PL\SQL设置中文
  8. 这两天测试软件发现的问题
  9. 利用tomcat配置网站
  10. java多线程设计模式:wait/notify机制(转)