由BNF解释如何用递归表示循环

      正巧老夫最近在做C99语法分析的工作,看了<华丽的递归...>的帖子,里面用几个简单的双向递归模拟循环,突然有点儿心得,贴出来分享一下,有错误的地方还请各位看官指正。

      先看看3种不同的BNF的产生式,由这三个产生式可以回答二个问题

      1 什么样的递归可以用不带栈的循环表示 ?

      2 怎么用递归表示循环 ?

   1                   2                   3
A : 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/p/2276836.html