不知道如何解决 SICP 练习 1.11
函数
f
由以下规则定义:如果n
且如果f(n) = n
3n > 则
.编写一个通过递归过程计算f(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3)
3.f
的过程。编写一个通过迭代过程计算f
的过程。
递归地实现它非常简单。但我不知道如何迭代地做到这一点。我尝试与给出的斐波那契示例进行比较,但我不知道如何使用它作为类比。所以我放弃了(为我感到羞耻)并在 Google 上搜索解释,我发现了这一点:
(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))))
读完之后,我了解了代码及其工作原理。但我不明白的是从函数的递归定义到此所需的过程。我不明白代码是如何在某人的头脑中形成的。
您能解释一下达成解决方案所需的思维过程吗?
A function
f
is defined by the rule thatf(n) = n
ifn < 3
andf(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3)
ifn > 3
. Write a procedure that computesf
by means of a recursive process. Write a procedure that computesf
by means of an iterative process.
Implementing it recursively is simple enough. But I couldn't figure out how to do it iteratively. I tried comparing with the Fibonacci example given, but I didn't know how to use it as an analogy. So I gave up (shame on me) and Googled for an explanation, and I found this:
(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))))
After reading it, I understand the code and how it works. But what I don't understand is the process needed to get from the recursive definition of the function to this. I don't get how the code could have formed in someone's head.
Could you explain the thought process needed to arrive at the solution?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您需要捕获某些累加器中的状态并在每次迭代时更新状态。
如果您有使用命令式语言的经验,请想象编写一个 while 循环并在循环的每次迭代期间跟踪变量中的信息。你需要什么变量?您将如何更新它们?这正是您在Scheme 中进行迭代(尾递归)调用集所必须要做的。
换句话说,开始将其视为 while 循环而不是递归定义可能会有所帮助。最终你会足够流利地使用递归 ->迭代转换,您不需要额外的帮助即可开始。
对于这个特定的示例,您必须仔细查看三个函数调用,因为并不能立即清楚如何表示它们。然而,这是可能的思考过程:(在Python伪代码中强调命令性)
每个递归步骤都会跟踪三件事:
所以我需要三个状态来跟踪
的当前值、最后一个值和倒数第二个值f。 (即
f(n-1)、f(n-2) 和 f(n-3)
。)将它们称为a、b、c
。我必须在每个循环内更新这些部分:那么 NEWVALUE 是什么?好吧,现在我们有了 f(n-1)、f(n-2) 和 f(n-3) 的表示,这只是递归方程:
现在剩下的就是弄清楚
a、b 和 c
的初始值。但这很容易,因为我们知道如果 nf(n) = n 3.
.这与Scheme迭代版本还是有点不同,但我希望你现在能看到这个思考过程。
You need to capture the state in some accumulators and update the state at each iteration.
If you have experience in an imperative language, imagine writing a while loop and tracking information in variables during each iteration of the loop. What variables would you need? How would you update them? That's exactly what you have to do to make an iterative (tail-recursive) set of calls in Scheme.
In other words, it might help to start thinking of this as a while loop instead of a recursive definition. Eventually you'll be fluent enough with recursive -> iterative transformations that you won't need to extra help to get started.
For this particular example, you have to look closely at the three function calls, because it's not immediately clear how to represent them. However, here's the likely thought process: (in Python pseudo-code to emphasise the imperativeness)
Each recursive step keeps track of three things:
So I need three pieces of state to track the current, the last and the penultimate values of
f
. (that is,f(n-1), f(n-2) and f(n-3)
.) Call thema, b, c
. I have to update these pieces inside each loop:So what's NEWVALUE? Well, now that we have representations of
f(n-1), f(n-2) and f(n-3)
, it's just the recursive equation:Now all that's left is to figure out the initial values of
a, b and c
. But that's easy, since we know thatf(n) = n if n < 3
.That's still a little different from the Scheme iterative version, but I hope you can see the thought process now.
我认为您是在问人们如何在“设计模式”之外自然地发现算法。
查看 f(n) 在每个 n 值处的展开对我很有帮助:
仔细观察 f(3),我们发现我们可以立即根据已知值计算它。
我们需要什么来计算 f(4)?
我们至少需要计算 f(3) + [其余部分]。但是当我们计算 f(3) 时,我们也会计算 f(2) 和 f(1),而我们恰好需要这些来计算 f(4) 的[其余部分]。
因此,对于任何数字 n,我可以从计算 f(3) 开始,然后重用用于计算 f(3) 的值来计算 f(4)...并且该模式继续...
因为我们将重用它们,让我们给它们命名 a、b、c。以我们正在进行的步骤为下标,并进行 f(5) 的计算:
其中
a1 = f(2) = 2,
b1 = f(1 ) = 1,
c1 = 0
因为 f(n) = n (n 1) 3.
因此:
f(3) = a1 + 2b1 + 3c1 = 4
因此:
a2 = f(3) = 4(在上面步骤 1 中计算),
b2 = a1 = f(2) = 2,
c2 sub> = b1 = f(1) = 1
因此:
f(4) = 4 + 2*2 + 3*1 = 11
因此:
a3 = f (4) = 11(在上面的步骤 2 中计算),
b3 = a2 = f(3) = 4,
c3 = b2 = f(2) = 2
因此:
f(5) = 11 + 2*4 + 3*2 = 25
在整个上述计算中,我们捕获先前计算中的状态并将其传递给下一步,
特别是:
astep = 步骤结果 - 1
bstep = astep - 1
cstep = b< sub>step -1
一旦我看到这个,那么想出迭代版本就很简单了。
I think you are asking how one might discover the algorithm naturally, outside of a 'design pattern'.
It was helpful for me to look at the expansion of the f(n) at each n value:
Looking closer at f(3), we see that we can calculate it immediately from the known values.
What do we need to calculate f(4)?
We need to at least calculate f(3) + [the rest]. But as we calculate f(3), we calculate f(2) and f(1) as well, which we happen to need for calculating [the rest] of f(4).
So, for any number n, I can start by calculating f(3), and reuse the values I use to calculate f(3) to calculate f(4)...and the pattern continues...
Since we will reuse them, lets give them a name a, b, c. subscripted with the step we are on, and walk through a calculation of f(5):
where
a1 = f(2) = 2,
b1 = f(1) = 1,
c1 = 0
since f(n) = n for n < 3.
Thus:
f(3) = a1 + 2b1 + 3c1 = 4
So:
a2 = f(3) = 4 (calculated above in step 1),
b2 = a1 = f(2) = 2,
c2 = b1 = f(1) = 1
Thus:
f(4) = 4 + 2*2 + 3*1 = 11
So:
a3 = f(4) = 11 (calculated above in step 2),
b3 = a2 = f(3) = 4,
c3 = b2 = f(2) = 2
Thus:
f(5) = 11 + 2*4 + 3*2 = 25
Throughout the above calculation we capture state in the previous calculation and pass it to the next step,
particularily:
astep = result of step - 1
bstep = astep - 1
cstep = bstep -1
Once I saw this, then coming up with the iterative version was straightforward.
由于您链接的帖子描述了很多有关解决方案的信息,因此我将尝试仅提供补充信息。
您尝试在此处的方案中定义尾递归函数,并给出(非尾)递归定义。
递归的基本情况(如果 n < 3,则 f(n) = n)由两个函数处理。我不太确定作者为什么这样做;第一个函数可以简单地是:
一般形式是:
注意,我还没有填写 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),并递减计数。
如果这仍然没有帮助,请尝试问一个更具体的问题——当你写“我不明白”时,考虑到已经有相对详尽的解释,真的很难回答。
Since the post you linked to describes a lot about the solution, I'll try to only give complementary information.
You're trying to define a tail-recursive function in Scheme here, given a (non-tail) recursive definition.
The base case of the recursion (f(n) = n if n < 3) is handled by both functions. I'm not really sure why the author does this; the first function could simply be:
The general form would be:
Note I didn't fill in parameters for f-iter yet, because you first need to understand what state needs to be passed from one iteration to another.
Now, let's look at the dependencies of the recursive form of f(n). It references f(n - 1), f(n - 2), and f(n - 3), so we need to keep around these values. And of course we need the value of n itself, so we can stop iterating over it.
So that's how you come up with the tail-recursive call: we compute f(n) to use as f(n - 1), rotate f(n - 1) to f(n - 2) and f(n - 2) to f(n - 3), and decrement count.
If this still doesn't help, please try to ask a more specific question — it's really hard to answer when you write "I don't understand" given a relatively thorough explanation already.
我将采用与此处其他答案略有不同的方法来解决此问题,重点关注编码风格如何使此类算法背后的思维过程更容易理解。
比尔的方法的麻烦,在你的问题中引用的问题是,目前尚不清楚状态变量
a
、b
和c
所传达的含义 。他们的名字没有传达任何信息,比尔的帖子也没有描述他们遵守的任何不变规则或其他规则。我发现如果状态变量遵循一些描述它们彼此关系的记录规则,那么制定和理解迭代算法就更容易。考虑到这一点,请考虑完全相同算法的替代公式,它与 Bill 的不同之处仅在于为
a
、b
和c< 提供了更有意义的变量名称。 /code> 和一个递增的计数器变量而不是递减的计数器变量:
突然间,算法的正确性以及其创建背后的思维过程变得很容易看到和描述。计算
f(n)
:i
,它从 2 开始,逐渐增加到n
,每次调用时加 1f-iter
。f(i)
、f(i-1)
和f(i-2)
,这足以让我们计算f(i+1)
。i=n
,我们就完成了。I'm going to come at this in a slightly different approach to the other answers here, focused on how coding style can make the thought process behind an algorithm like this easier to comprehend.
The trouble with Bill's approach, quoted in your question, is that it's not immediately clear what meaning is conveyed by the state variables,
a
,b
, andc
. Their names convey no information, and Bill's post does not describe any invariant or other rule that they obey. I find it easier both to formulate and to understand iterative algorithms if the state variables obey some documented rules describing their relationships to each other.With this in mind, consider this alternative formulation of the exact same algorithm, which differs from Bill's only in having more meaningful variable names for
a
,b
andc
and an incrementing counter variable instead of a decrementing one:Suddenly the correctness of the algorithm - and the thought process behind its creation - is simple to see and describe. To calculate
f(n)
:i
that starts at 2 and climbs ton
, incrementing by 1 on each call tof-iter
.f(i)
,f(i-1)
andf(i-2)
, which is sufficient to allow us to calculatef(i+1)
.i=n
, we are done.对我有帮助的是使用铅笔手动运行该流程,并使用作者为斐波那契示例提供的提示
将其转化为新问题是如何在流程中推动状态
因此您需要一个带有接口的函数来接受3个变量:
a、b、c
。并且它需要使用上面的过程来调用自身。如果您运行并打印以
(f-iter 1 0 0)
开头的每次迭代的每个变量,您将得到类似这样的结果(当然它将永远运行):你能看到答案吗?您可以通过对每次迭代的 b 列和 c 列求和来得到它。我必须承认我通过一些尝试和错误找到了它。剩下的唯一的事情就是有一个计数器来知道何时停止,这就是整个事情:
What did help me was running the process manually using a pencil and using hint author gave for the fibonacci example
Translating this to new problem is how you push state forward in the process
So you need a function with an interface to accept 3 variables:
a, b, c
. And it needs to call itself using process above.If you run and print each variable for each iteration starting with
(f-iter 1 0 0)
, you'll get something like this (it will run forever of course):Can you see the answer? You get it by summing columns b and c for each iteration. I must admit I found it by doing some trail and error. Only thing left is having a counter to know when to stop, here is the whole thing:
已经写好了:
不管你信不信,曾经有这样一种语言< /a>.用另一种语言写下来只是语法问题。顺便说一句,您(错误)引用的定义有一个错误,现在非常明显且清晰。
迭代意味着前进(有 你的解释!)而不是首先向后递归到最低级别,然后在返回的过程中向前计算结果:
因此,这将问题的状态转换描述为
我们可以将其编码为,
但它当然永远不会停止。因此,我们必须改为拥有
,并且这已经与您询问的代码完全相同,就语法而言。
在这里,按照我们的“前进”范式,向上计数到 n 更自然,但是像您引用的代码那样向下计数到 0 当然是完全等效的。
极端情况和可能的相差一错误将作为
练习无趣的技术细节而被忽略。It is already written:
Believe it or not, there was once such a language. To write this down in another language is just a matter of syntax. And by the way, the definition as you (mis)quote it has a bug, which is now very apparent and clear.
Iteration means going forward (there's your explanation!) as opposed to the recursion's going backwards at first, to the very lowest level, and then going forward calculating the result on the way back up:
This thus describes the problem's state transitions as
We could code it as
but of course it wouldn't ever stop. So we must instead have
and this is already exactly like the code you asked about, up to syntax.
Counting up to n is more natural here, following our paradigm of "going forward", but counting down to 0 as the code you quote does is of course entirely equivalent.
The corner cases and possible off-by-one errors are left out as
exercisenon-interesting technicalities.