不知道如何解决SICP练习1.11

时间:2010-03-02 19:18:17

标签: recursion scheme iteration sicp

Exercise 1.11

  

功能ff(n) = n n < 3f(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3) n > 3的规则定义。编写一个通过递归过程计算f的过程。编写一个通过迭代过程计算f的过程。

递归地实现它很简单。但我无法弄清楚如何迭代地做到这一点。我尝试与给出的Fibonacci示例进行比较,但我不知道如何将其用作类比。所以我放弃了(羞辱我)并用Google搜索an explanation,我发现了这个:

(define (f n)
   (if (< n 3)
       n
       (f-iter 2 1 0 n)))

(define (f-iter a b c count)
   (if (< count 3)
       a
       (f-iter (+ a (* 2 b) (* 3 c))
               a
               b
               (- count 1))))

阅读之后,我理解代码及其工作原理。但我不明白的是从函数的递归定义到此需要的过程。我不明白代码是如何在某人的头脑中形成的。

您能解释一下解决方案所需的思考过程吗?

6 个答案:

答案 0 :(得分:29)

您需要捕获某些累加器中的状态并在每次迭代时更新状态。

如果您有使用命令式语言的经验,请设想编写while循环并在循环的每次迭代期间跟踪变量中的信息。你需要什么变量?你会如何更新它们?这正是你在Scheme中进行迭代(尾递归)调用的必要条件。

换句话说,开始将其视为while循环而不是递归定义可能会有所帮助。最终你会用递归的流畅 - &gt;迭代转换,你不需要额外的帮助来开始。


对于这个特定的例子,你必须仔细观察三个函数调用,因为它不能立即清楚如何表示它们。然而,这是可能的思考过程:(在Python伪代码中强调命令性)

每个递归步骤都会跟踪三件事:

f(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3) 

所以我需要三个状态来跟踪f的当前,最后和倒数第二个值。 (即f(n-1), f(n-2) and f(n-3)。)将其称为a, b, c。我必须在每个循环中更新这些部分:

for _ in 2..n:
    a = NEWVALUE
    b = a
    c = b
return a

那么什么是NEWVALUE?好吧,现在我们有f(n-1), f(n-2) and f(n-3)的表示,它只是递归方程式:

for _ in 2..n:
    a = a + 2 * b + 3 * c
    b = a
    c = b
return a

现在剩下的就是找出a, b and c的初始值。但这很容易,因为我们知道f(n) = n if n < 3

if n < 3: return n
a = 2 # f(n-1) where n = 3
b = 1 # f(n-2)
c = 0 # f(n-3)
# now start off counting at 3
for _ in 3..n:
    a = a + 2 * b + 3 * c
    b = a
    c = b
return a

这仍然与Scheme迭代版本略有不同,但我希望你现在可以看到思考过程。

答案 1 :(得分:14)

我认为你问的是如何在“设计模式”之外自然地发现算法。

在每个n值处查看f(n)的扩展是有帮助的:

f(0) = 0  |
f(1) = 1  | all known values
f(2) = 2  |

f(3) = f(2) + 2f(1) + 3f(0)
f(4) = f(3) + 2f(2) + 3f(1)
f(5) = f(4) + 2f(3) + 3f(2)
f(6) = f(5) + 2f(4) + 3f(3)

仔细观察f(3),我们发现我们可以从已知值立即计算出来。 我们需要计算f(4)?

我们至少需要计算f(3)+ [其余]。但是当我们计算f(3)时,我们也计算f(2)和f(1),我们碰巧需要计算f(4)的[其余]。

f(3) = f(2) + 2f(1) + 3f(0)
            ↘       ↘
f(4) = f(3) + 2f(2) + 3f(1)

因此,对于任何数字n,我可以从计算f(3)开始,并重用我用来计算f(3)的值来计算f(4)......并且模式继续...

f(3) = f(2) + 2f(1) + 3f(0)
            ↘       ↘
f(4) = f(3) + 2f(2) + 3f(1)
            ↘       ↘
f(5) = f(4) + 2f(3) + 3f(2)

由于我们将重用它们,让我们给它们一个名字a,b,c。下载我们所处的步骤,并完成f(5)的计算:

  Step 1:    f(3) = f(2) + 2f(1) + 3f(0) or f(3) = a1 + 2b1 +3c1

,其中

a 1 = f(2)= 2,

b 1 = f(1)= 1,

c 1 = 0

因为f(n)= n,n

因此:

f(3)= a 1 + 2b 1 + 3c 1 = 4

  Step 2:  f(4) = f(3) + 2a1 + 3b1

所以:

a 2 = f(3)= 4(上面在步骤1中计算),

b 2 = a 1 = f(2)= 2,

c 2 = b 1 = f(1)= 1

因此:

f(4)= 4 + 2 * 2 + 3 * 1 = 11

  Step 3:  f(5) = f(4) + 2a2 + 3b2

所以:

a 3 = f(4)= 11(上面在步骤2中计算),

b 3 = a 2 = f(3)= 4,

c 3 = b 2 = f(2)= 2

因此:

f(5)= 11 + 2 * 4 + 3 * 2 = 25

在上面的计算中,我们捕获先前计算中的状态并将其传递给下一步, particularily:

步骤 =步骤1的结果

b step = a step - 1

c 步骤 = b 步骤-1

一旦我看到这一点,那么提出迭代版本就很简单了。

答案 2 :(得分:2)

由于你链接的帖子描述了很多关于解决方案的内容,我将尝试仅提供补充信息。

在这里,你试图在Scheme中定义一个尾递归函数,给定一个(非尾部)递归定义。

递归的基本情况(f(n)= n,如果n <3)由两个函数处理。我不确定作者为什么会这样做;第一个功能可能只是:

(define (f n)
   (f-iter 2 1 0 n))

一般形式是:

(define (f-iter ... n)
   (if (base-case? n)
       base-result
       (f-iter ...)))

注意我还没有填写f-iter的参数,因为你首先需要了解需要从一次迭代传递到另一次迭代的状态。

现在,让我们看一下f(n)的递归形式的依赖关系。它引用f(n - 1),f(n - 2)和f(n - 3),因此我们需要保持这些值。当然我们需要n本身的值,所以我们可以停止迭代它。

这就是你如何提出尾递归调用:我们计算f(n)用作f(n - 1),将f(n - 1)旋转到f(n - 2)和f(n) - 2)到f(n - 3),并递减计数。

如果这仍然无济于事,请尝试提出一个更具体的问题 - 如果您已经写了“我不明白”,那么很难回答这个问题。

答案 3 :(得分:1)

我将采用与此处其他答案略有不同的方法来解决这个问题,重点关注编码风格如何使像这样的算法背后的思维过程更容易理解。

问题中引用Bill's approach的问题在于,目前尚不清楚状态变量ab传达的含义是什么和c。他们的名字没有传达信息,比尔的帖子没有描述他们遵守的任何不变或其他规则。如果状态变量遵循描述它们之间关系的一些记录规则,我发现制定和理解迭代算法都更容易。

考虑到这一点,请考虑完全相同算法的替代公式,这与Bill的不同之处仅在于为abc提供更有意义的变量名称并且增加计数器变量而不是递减的:

(define (f n)
    (if (< n 3)
        n
        (f-iter n 2 0 1 2)))

(define (f-iter n 
                i 
                f-of-i-minus-2
                f-of-i-minus-1
                f-of-i)
    (if (= i n)
        f-of-i
        (f-iter n
                (+ i 1)
                f-of-i-minus-1
                f-of-i
                (+ f-of-i
                   (* 2 f-of-i-minus-1)
                   (* 3 f-of-i-minus-2)))))

突然间,算法的正确性 - 以及其创建背后的思维过程 - 很容易看到和描述。要计算f(n)

  • 我们有一个计数器变量i,从2开始爬升到n,每次调用f-iter时递增1。
  • 在整个过程中的每一步,我们都会跟踪f(i)f(i-1)f(i-2),这足以让我们计算f(i+1)
  • 一旦i=n,我们就完成了。

答案 4 :(得分:1)

  

功能ff(n) = n, if n<3f(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3), if n > 3规则定义。编写一个通过递归过程计算f的过程。

已写入:

f(n) = n,                                  (* if *)  n < 3
     = f(n - 1) + 2f(n - 2) + 3f(n - 3),   (* if *)  n > 3

信不信由你,曾经such a language。用另一种语言写下这只是语法问题。顺便说一句,你(错误)引用它的定义有一个错误,现在非常明显和明确。

  

编写一个通过迭代过程计算f的过程。

迭代意味着going forward你的解释!)而不是递归首先向向后

f(n)   =  f(n - 1) + 2f(n - 2) + 3f(n - 3) 
       =  a        + 2b        + 3c
f(n+1) =  f(n    ) + 2f(n - 1) + 3f(n - 2)
       =  a'       + 2b'       + 3c'          a' = a+2b+3c, b' = a, c' = b
......

这样就将问题的状态转换描述为

 (n, a, b, c)  ->  (n+1, a+2*b+3*c, a, b)

我们可以将其编码为

g (n, a, b, c) = g (n+1, a+2*b+3*c, a, b)

但当然不会停止。所以我们必须改为

f n = g (2, 2, 1, 0)
  where
  g (k, a, b, c) = g (k+1, a+2*b+3*c, a, b),    (* if *) k < n
  g (k, a, b, c) = a,                           otherwise 

这已经完全像你提到的代码一样,直到语法。

计算到 n 在这里更自然,遵循我们的“前进”范例,但是倒数到 0 ,因为你引用的代码当然完全是等效。

角落案件和可能的一对一错误被排除在练习非有趣的技术背景之外。

答案 5 :(得分:0)

是什么帮助我使用铅笔手动运行了该过程,并使用了提示作者作为斐波那契示例提供的信息

a <- a + b
b <- a

将其转化为新问题是您如何在此过程中推动状态

a <- a + (b * 2) + (c * 3)
b <- a
c <- b

因此,您需要一个带有接口的函数来接受3个变量:a, b, c。它需要使用上面的过程来调用自己。

(define (f-iter a b c)
  (f-iter (+ a (* b 2) (* c 3)) a b))

如果您运行并打印从(f-iter 1 0 0)开始的每个迭代的每个变量,您将得到类似的信息(它将永远运行):

a   b   c
=========
1   0   0
1   1   0
3   1   1
8   3   1
17  8   3
42  17  8
100 42  17
235 100 42
...

您看到答案了吗?您可以通过对每次迭代的b和c列求和来获得它。我必须承认我是通过一些跟踪和错误发现的。剩下的就是要知道何时停止的计数器,这是整个过程:

(define (f n)
  (f-iter 1 0 0 n))

(define (f-iter a b c count)
  (if (= count 0)
      (+ b c)
      (f-iter (+ a (* b 2) (* c 3)) a b (- count 1))))