相互递归(2)

2019-05-26 19:10:10 浏览数 (1)

代码语言:javascript复制
  版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址

  http://www.cnblogs.com/Colin-Cai/p/10920847.html 

  作者:窗户

  QQ/微信:6679072

  E-mail:6679072@qq.com

  本章继续上一章,说明一下这个问题:

  所有的相互递归都可以被转化为一般的递归,从而最终可以用lambda演算来完成。

  假设有以下对于f_1,f_2, ... f_n 的相互递归:

  `$f{1} = F{1}(f{1}, f{2}, ... f_{n})

$`

  f_{2} = F_{2}(f_{1}, f_{2}, ... f_{n})

  ...

  f_{n} = F_{n}(f_{1}, f_{2}, ... f_{n})

  如果我们定义一个高阶函数(算子)f,满足

  f_{1} = f(1)

  f_{2} = f(2)

  ...

  f_{n} = f(n)

  代入上式,得到

  f(1) = F_{1}(f(1), f(2), ... f(n))

  f(2) = F_{2}(f(1), f(2), ... f(n))

  ...

  f(n) = F_{n}(f(1), f(2), ... f(n))

  于是以上就是一个对于f的普通递归(f递归到f)。

  从而,我们就知道了,任何递归都可以转化为到自身的普通递归

  然而,对于lambda演算,因为自身没有名字,那又如何递归呢?

  我们就用非负整数的最大公约数为例子,还是用Scheme,一步步来。

  我们记gcd(a_1,a_2, ..., a_n)a_1,a_2, ..., a_n 的最大公约数。

  最大公约数的递归其实很简单:

  (1)gcd(0, a) = a

  (2)如果a不等于0,那么 gcd(a, b) = gcd(b%a, a) ,此处%是取余数

  (3)gcd(a_1, a_2, ... a_n) = gcd(a_1, gcd(a_2, ... a_n))

  其中第一条、第二条连续使用就是著名的欧几里得算法,或者称辗转相除法。

  而第三条则用于缩减最大公约数求解的个数,之前我在文章《汉诺塔——各种编程范式的解决》提到,递归可求解的真谛在于缩小问题处理的规模以达到降阶,以上第二、三条则是可以达到降阶的效果。

  于是上述三条再加上gcd() = 0gcd(0) = 0 这两条边界条件,用Scheme描述递归如下:

代码语言:javascript复制
(define gcd
 (lambda s
  (if (null? s)
   0
   (if (zero? (car s))
    (apply gcd (cdr s))
    (if (null? (cdr s))
     (car s)
     (if (null? (cddr s))
      (gcd (remainder (cadr s) (car s)) (car s))
      (gcd (car s) (apply gcd (cdr s)))
     ))))))

  为了实现匿名递归,也就是我们最终希望在lambda演算中递归,我们需要考虑以下函数

代码语言:javascript复制
(define g
 (lambda (f)
  (lambda s
   (if (null? s)
    0
    (if (zero? (car s))
     (apply f (cdr s))
     (if (null? (cdr s))
      (car s)
      (if (null? (cddr s))
       (f (remainder (cadr s) (car s)) (car s))
       (f (car s) (apply f (cdr s)))
      )))))))

  我们此处好好想一想,会发现,g(gcd) = gcd

  也就是gcd是函数g的不动点。

  其实不动点在其他函数中一样存在,比如f(x) = x 的不动点是0,

  只是这里的函数是高阶函数(算子),似乎挺拗口。

  假如有个函数Y(当然,这个Y也是一个算子)可以找到算子的不动点,比如使得$g(Y(g)) = Y(g)$,那么Y(g)就是我们本来想要实现的gcd,

  于是我们就通过lambda演算实现了匿名递归。

  那么这样的Y存在吗?

  幸运的是,Y函数是存在的,有个学名叫Y combinator,我们知道美国有个孵化器公司叫这个名字,实际上就是取这个的意义。这个早在Church创建lambda验算体系的时候就已经发现,而且至关重要,否则就不知道怎么递归了。

  Scheme下,Y combinator可以如下

代码语言:javascript复制
(lambda (f)
 ((lambda (g) (g g))
  (lambda (x) (f (lambda s (apply (x x) s))))))

  因为gcd函数可以表示为Y(g)的形式,

  于是,我们的gcd就可以形式如下

代码语言:javascript复制
(define gcd
 ((lambda (f)
   ((lambda (g) (g g))
    (lambda (x) (f (lambda s (apply (x x) s))))))
  (lambda (f)
   (lambda s
    (if (null? s)
     0
     (if (zero? (car s))
      (apply f (cdr s))
      (if (null? (cdr s))
       (car s)
       (if (null? (cddr s))
        (f (remainder (cadr s) (car s)) (car s))
        (f (car s) (apply f (cdr s)))
       ))))))))

  于是,我们发现gcd的定义过程中,只用到了lambda演算,从而lambda演算统一了一切!

  靠谱吗?那么我们用上述定义的如此诡异的gcd随便运算一下几组最大公约数

(display (gcd 225 150 165))

  得到

15

0 人点赞