diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 237b9ef9..69855ee5 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -1,4 +1,4 @@ -The solution using a loop: +使用循环的解法: ```js run function sumTo(n) { @@ -12,7 +12,7 @@ function sumTo(n) { alert( sumTo(100) ); ``` -The solution using recursion: +使用递归的解法: ```js run function sumTo(n) { @@ -23,7 +23,7 @@ function sumTo(n) { alert( sumTo(100) ); ``` -The solution using the formula: `sumTo(n) = n*(n+1)/2`: +使用右边公式的解法: `sumTo(n) = n*(n+1)/2`: ```js run function sumTo(n) { @@ -33,8 +33,8 @@ function sumTo(n) { alert( sumTo(100) ); ``` -P.S. Naturally, the formula is the fastest solution. It uses only 3 operations for any number `n`. The math helps! +附注:使用公式的解法显然是最快的,针对任何数字,它都只需要三个运算;关键时刻还是数学有用啊! -The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. +以速度论,使用循环变量的方法第二快。无论是使用递归方法还是使用循环的方法,我们都要累加相同的数字,但递归方法需要嵌套调用并需要在执行中管理堆栈,这些都需要额外的资源,因此慢些。 -P.P.S. The standard describes a "tail call" optimization: if the recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution and we don't need to remember its execution context. In that case `sumTo(100000)` is countable. But if your JavaScript engine does not support it, there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +又注:标准中关于“尾部调用”优化的描述是如果递归调用发生在函数的最后(如同上面的`sumTo`),那么外部调用函数就不用记忆执行的上下文背景,因为在调用返回后没有需要继续执行的代码。在这种情况下,即便是`sumTo(100000)`也不是什么问题。但如果你的JavaScript引擎不支持这样的优化,那么因为通常的引擎对堆栈的总数有限制,你会遇到“超过最大堆栈尺寸”的错误。 diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md index cabc1329..fc5d296c 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md @@ -1,12 +1,12 @@ -importance: 5 +重要度: 5 --- # Sum all numbers till the given one -Write a function `sumTo(n)` that calculates the sum of numbers `1 + 2 + ... + n`. +编写一个函数: `sumTo(n)`,计算以下数字的和: `1 + 2 + ... + n`. -For instance: +例如: ```js no-beautify sumTo(1) = 1 @@ -17,13 +17,13 @@ sumTo(4) = 4 + 3 + 2 + 1 = 10 sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050 ``` -Make 3 solution variants: +使用三种不同的解法: -1. Using a for loop. -2. Using a recursion, cause `sumTo(n) = n + sumTo(n-1)` for `n > 1`. -3. Using the [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) formula. +1. 使用循环. +2. 使用递归,在`'n > 1`时, `sumTo(n) = n + sumTo(n-1)` . +3. 使用数学公式 [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) formula. -An example of the result: +结果示例: ```js function sumTo(n) { /*... your code ... */ } @@ -31,6 +31,6 @@ function sumTo(n) { /*... your code ... */ } alert( sumTo(100) ); // 5050 ``` -P.S. Which solution variant is the fastest? The slowest? Why? +附注:那种方案最快?那种最慢?为啥? -P.P.S. Can we use recursion to count `sumTo(100000)`? +又注:我们能用递归方法计算 `sumTo(100000)`吗? diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md index 59040a2b..7433e340 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md @@ -1,6 +1,6 @@ -By definition, a factorial is `n!` can be written as `n * (n-1)!`. +根据定义,阶乘 `n!` 等于 `n * (n-1)!`. -In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`. +换言之,函数 `factorial(n)` 的结果可以通过 `n` 乘以函数 `factorial(n-1)`的结果. 然后针对 `n-1` 的计算可以以此类推,递归地降低下来直到 `1`. ```js run function factorial(n) { @@ -10,7 +10,7 @@ function factorial(n) { alert( factorial(5) ); // 120 ``` -The basis of recursion is the value `1`. We can also make `0` the basis here, doesn't matter much, but gives one more recursive step: +递归的基本值可以是 `1`. 我们也可以选择 `0` 作为基本值, 除了多算一步之外没有差别( `0` 的阶乘结果是 `1`: ```js run function factorial(n) { diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/task.md b/1-js/06-advanced-functions/01-recursion/02-factorial/task.md index d2aef2d9..39e816b1 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/task.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/task.md @@ -1,18 +1,18 @@ -importance: 4 +重要度: 4 --- -# Calculate factorial +# 计算斐波拉契数 -The [factorial](https://en.wikipedia.org/wiki/Factorial) of a natural number is a number multiplied by `"number minus one"`, then by `"number minus two"`, and so on till `1`. The factorial of `n` is denoted as `n!` +自然数的阶乘[factorial](https://en.wikipedia.org/wiki/Factorial) 是一个数乘以该数减一,然后再乘以该数减二,一直到1为止。`n`的阶乘表示为:`n!` -We can write a definition of factorial like this: +我们可以这样定义阶乘:: ```js n! = n * (n - 1) * (n - 2) * ...*1 ``` -Values of factorials for different `n`: +不同自然数阶乘的结果 `n`: ```js 1! = 1 @@ -22,10 +22,10 @@ Values of factorials for different `n`: 5! = 5 * 4 * 3 * 2 * 1 = 120 ``` -The task is to write a function `factorial(n)` that calculates `n!` using recursive calls. +这个任务是使用递归的方法编写一个函数 `factorial(n)` 来计算 `n!` . ```js alert( factorial(5) ); // 120 ``` -P.S. Hint: `n!` can be written as `n * (n-1)!` For instance: `3! = 3*2! = 3*2*1! = 6` +附注,提示: `n!` 可被写作 `n * (n-1)!` ,例如: `3! = 3*2! = 3*2*1! = 6` diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md index 91bcecc0..7fabdd17 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md @@ -1,6 +1,6 @@ -The first solution we could try here is the recursive one. +第一个能够应用的解法是使用递归。 -Fibonacci numbers are recursive by definition: +斐波拉契数列按照定义是递归性质的: ```js run function fib(n) { @@ -12,11 +12,11 @@ alert( fib(7) ); // 13 // fib(77); // will be extremely slow! ``` -...But for big values of `n` it's very slow. For instance, `fib(77)` may hang up the engine for some time eating all CPU resources. +...但这种解法在 `n` 很大时会非常慢。 例如: `fib(77)` 会将引擎挂起一段时间并消耗所有的 CPU 资源。 -That's because the function makes too many subcalls. The same values are re-evaluated again and again. +这是因为这样的函数需要调用太多次,相同的数字需要一再被计算。 -For instance, let's see a piece of calculations for `fib(5)`: +以 `fib(5)` 为例,来看看有哪些调用与计算发生: ```js no-beautify ... @@ -25,23 +25,23 @@ fib(4) = fib(3) + fib(2) ... ``` -Here we can see that the value of `fib(3)` is needed for both `fib(5)` and `fib(4)`. So `fib(3)` will be called and evaluated two times completely independently. +这里我们可以看到计算 `fib(5)` 与 `fib(4)` 时,都需要 `fib(3)` 的值。所有 `fib(3)` 需要至少两次完全独立的调用与计算。 -Here's the full recursion tree: +完整的递归调用可以从这里的树状图看出: ![fibonacci recursion tree](fibonacci-recursion-tree.png) -We can clearly notice that `fib(3)` is evaluated two times and `fib(2)` is evaluated three times. The total amount of computations grows much faster than `n`, making it enormous even for `n=77`. +我们可以很清晰地看到, `fib(3)` 被计算了两次,且 `fib(2)` 被计算了三次。 总的计算次数增长的速度超过了 `n` 的增长速度, 当 `n=77` 时这个差异就很明显了。 -We can optimize that by remembering already-evaluated values: if a value of say `fib(3)` is calculated once, then we can just reuse it in future computations. +我们可以通过记住已经计算过的数字的方式来优化: 如果 `fib(3)` 的结果已经计算出,那么我们可以记住它并在以后的计算中直接使用。 -Another variant would be to give up recursion and use a totally different loop-based algorithm. +另外一种变通方案是放弃递归,使用完全基于循环的算法。 -Instead of going from `n` down to lower values, we can make a loop that starts from `1` and `2`, then gets `fib(3)` as their sum, then `fib(4)` as the sum of two previous values, then `fib(5)` and goes up and up, till it gets to the needed value. On each step we only need to remember two previous values. +与从 `n` 由大到小计算的方案相反,我们可以从 `1` 和 `2` 开始循环,得到 `fib(3)` 的值,然后根据之前两个值的和计算出 `fib(4)` ,然后计算出 `fib(5)` ,以此类推,直到计算到所需要的值。用这种方法,我们就只需要记下前两个值。 -Here are the steps of the new algorithm in details. +新算法的计算步骤如下。 -The start: +开始: ```js // a = fib(1), b = fib(2), these values are by definition 1 @@ -56,9 +56,9 @@ a b c */ ``` -Now we want to get `fib(4) = fib(2) + fib(3)`. +现在我们想通过 `fib(4) = fib(2) + fib(3)` 得到 `4` 的计算结果。 -Let's shift the variables: `a,b` will get `fib(2),fib(3)`, and `c` will get their sum: +我们可以将 `fib(2),fib(3)` 在前一步计算出来的结果放入 `a,b` , 那么 `c` 所包含的就是他们的和: ```js no-beautify a = b; // now a = fib(2) @@ -71,7 +71,7 @@ c = a + b; // c = fib(4) */ ``` -The next step gives another sequence number: +再继续一轮,就可以得到下一个数列的值: ```js no-beautify a = b; // now a = fib(3) @@ -84,9 +84,9 @@ c = a + b; // c = fib(5) */ ``` -...And so on until we get the needed value. That's much faster than recursion and involves no duplicate computations. +以此类推,直到我们到达想要计算的值。 这种方法就比递归快很多,并且没有重复的计算。 -The full code: +完整的代码如下: ```js run function fib(n) { @@ -105,6 +105,6 @@ alert( fib(7) ); // 13 alert( fib(77) ); // 5527939700884757 ``` -The loop starts with `i=3`, because the first and the second sequence values are hard-coded into variables `a=1`, `b=1`. +因为数列中的第一个、第二个值已经通过变量赋值 `a=1`, `b=1` 进行了硬编码,因此循环可以从 `i=3` 开始。 -The approach is called [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming). +这种方法又称之为:自底向上的动态编程 [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming) 。 diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md index 3cdadd21..86a3ff7d 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md @@ -1,18 +1,18 @@ -importance: 5 +重要度: 5 --- # Fibonacci numbers -The sequence of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) has the formula Fn = Fn-1 + Fn-2. In other words, the next number is a sum of the two preceding ones. +满足公式 Fn = Fn-1 + Fn-2 的数列被称为: [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) , 也即:下一个数是前两个数的和。 -First two numbers are `1`, then `2(1+1)`, then `3(1+2)`, `5(2+3)` and so on: `1, 1, 2, 3, 5, 8, 13, 21...`. +最前的两个数都是 `1`, 接着是 `2(1+1)`, 然后是 `3(1+2)`, `5(2+3)` ,以此类推,后续的数字为: `1, 1, 2, 3, 5, 8, 13, 21...`. -Fibonacci numbers are related to the [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) and many natural phenomena around us. +斐波拉契数列与黄金分割 [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) 以及很多身边的自然现象有关。 -Write a function `fib(n)` that returns the `n-th` Fibonacci number. +请编写一个返回第n `n-th` 个斐波拉契数字的函数 `fib(n)` 。 -An example of work: +示例: ```js function fib(n) { /* your code */ } @@ -22,4 +22,4 @@ alert(fib(7)); // 13 alert(fib(77)); // 5527939700884757 ``` -P.S. The function should be fast. The call to `fib(77)` should take no more than a fraction of a second. +附注: 这个函数运行起来应该足够快,调用 `fib(77)` 所需的时间应该不超过一秒钟。 diff --git a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md index 4e9de146..81b459c7 100644 --- a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md +++ b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md @@ -1,6 +1,6 @@ -# Loop-based solution +# 使用循环的方案 -The loop-based variant of the solution: +使用循环的方案: ```js run let list = { @@ -30,7 +30,7 @@ function printList(list) { printList(list); ``` -Please note that we use a temporary variable `tmp` to walk over the list. Technically, we could use a function parameter `list` instead: +请注意这里我们使用了一个临时变量 `tmp`来走遍列表. 技术上,我们可以使用一个函数参数 `list` : ```js function printList(list) { @@ -43,15 +43,15 @@ function printList(list) { } ``` -...But that would be unwise. In the future we may need to extend a function, do something else with the list. If we change `list`, then we loose such ability. +...但这种方式可能不明智,未来我们可能需要扩展一个函数,对列表做些别的事情。如果我们改成 `list`,我们可能会丧失一些灵活性。 -Talking about good variable names, `list` here is the list itself. The first element of it. And it should remain like that. That's clear and reliable. +说到变量的命名,这里的 `list`是列表本身,也是列表的第一个元素。我们应该保持这种风格,因为可读性高且易于维护。 -From the other side, the role of `tmp` is exclusively a list traversal, like `i` in the `for` loop. +另外一面, `tmp` 的角色就是专门用于列表的遍历变量,如同`for` 循环中的 `i`一样。 -# Recursive solution +# 递归解法 -The recursive variant of `printList(list)` follows a simple logic: to output a list we should output the current element `list`, then do the same for `list.next`: +问题 `printList(list)` 的递归解法使用一个简单的逻辑:当需要输出一个列表时,输出当前元素 `list` ,然后再输出当前元素的下一个元素 `list.next`: ```js run let list = { @@ -70,10 +70,10 @@ let list = { function printList(list) { - alert(list.value); // output the current item + alert(list.value); // 输出当前元素 if (list.next) { - printList(list.next); // do the same for the rest of the list + printList(list.next); // 如法炮制,输出后面的。 } } @@ -81,8 +81,8 @@ function printList(list) { printList(list); ``` -Now what's better? +那现在看看哪个更好? -Technically, the loop is more effective. These two variants do the same, but the loop does not spend resources for nested function calls. +技术上来说,循环解法的效率更高。两种解法殊途同归,但循环解法无需为嵌套调用耗费额外的资源。 -From the other side, the recursive variant is shorter and sometimes easier to understand. +事情的另外一面是:递归解法代码更短并且很多时候这也意味着更容易理解。 diff --git a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md index 1076b952..ef335074 100644 --- a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md +++ b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md @@ -1,10 +1,10 @@ -importance: 5 +重要度: 5 --- -# Output a single-linked list +# 输出单项链接的列表 -Let's say we have a single-linked list (as described in the chapter ): +假设我们有一个单向链接列表,如同章节中所描述的: ```js let list = { @@ -22,8 +22,8 @@ let list = { }; ``` -Write a function `printList(list)` that outputs list items one-by-one. +编写一个函数 `printList(list)` ,它能一个一个地输出每个列表中的元素。 -Make two variants of the solution: using a loop and using recursion. +请分别使用循环与递归实现。 -What's better: with recursion or without it? +使用递归与不使用递归,哪个更好? diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index a9ba0baf..cfa23ed3 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -1,8 +1,8 @@ -# Using a recursion +# 使用递归 -The recursive logic is a little bit tricky here. +递归逻辑在这里有点绕。 -We need to first output the rest of the list and *then* output the current one: +我们必须先输出列表的剩余部分,*然后* 再输出当前的这个元素: ```js run let list = { @@ -31,13 +31,13 @@ function printReverseList(list) { printReverseList(list); ``` -# Using a loop +# 使用循环 -The loop variant is also a little bit more complicated then the direct output. +使用循环的解法与上个章节相比也会复杂些。 -There is no way to get the last value in our `list`. We also can't "go back". +我们既不能直接获取列表`list`中的最后一个值,也不能“走回头路”。 -So what we can do is to first go through the items in the direct order and rememeber them in an array, and then output what we remembered in the reverse order: +所以,我们要做的首先是按照正常次序遍历整个列表,将每个元素记忆在一个数组中,然后再将数组中的元素逆序输出: ```js run let list = { @@ -71,4 +71,4 @@ function printReverseList(list) { printReverseList(list); ``` -Please note that the recursive solution actually does exactly the same: it follows the list, remembers the items in the chain of nested calls (in the execution context stack), and then outputs them. +请注意,递归解法其实做法完全一样,它先沿着列表遍历,将列表中的每个元素记忆在嵌套调用中(即:执行上下文堆栈中),然后再输出他们。 diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md index 81b1f3e3..d36a223c 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md @@ -1,9 +1,9 @@ -importance: 5 +重要度: 5 --- -# Output a single-linked list in the reverse order +# 以逆序输出一个单向链接的列表 -Output a single-linked list from the previous task in the reverse order. +将前面章节任务中的单向链接列表 以逆序方式输出。 -Make two solutions: using a loop and using a recursion. +要求:分别使用循环的解法与递归的解法。 diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 6995cdc4..ad3b1419 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -1,20 +1,20 @@ -# Recursion and stack +# 递归与堆栈 -Let's return to functions and study them more in-depth. +让我们回到函数做进一步的学习。 -Our first topic will be *recursion*. +我们的第一个主题是 *递归*。 -If you are not new to programming, then it is probably familiar and you could skip this chapter. +如果你不是一个编程新手,或许对此概念已经有所了解,那你可以跳过这个章节。 -Recursion is a programming pattern that is useful in situations when a task can be naturally split into several tasks of the same kind, but simpler. Or when a task can be simplified into an easy action plus a simpler variant of the same task. Or, as we'll see soon, to deal with certain data structures. +递归是一种编程模式,通常在以下情况下很有用:一个任务可以被自然地分解成多个类似但简单些的任务;或者是一个任务可以被简化成一个容易的动作并加上一个可以简化的、类似的任务变通;或者是为了应对一些特殊的数据结构。 -When a function solves a task, in the process it can call many other functions. A partial case of this is when a function calls *itself*. That's called *recursion*. +在一个完整一个特定的函数当中,该函数可以调用多个其他的函数。能够在函数中调用该函数自身的,是一种特殊情形,这种特殊情形就叫着: *递归*。 [cut] -## Two ways of thinking +## 两种思考的方法 -For something simple to start with -- let's write a function `pow(x, n)` that raises `x` to a natural power of `n`. In other words, multiplies `x` by itself `n` times. +让我们从一个简单的事情开始 -- 编写一个函数 `pow(x, n)` ,能够 `x` 的自然数 `n` 的次方。 换言之, `x` 与自身相乘 `n` 次。 ```js pow(2, 2) = 4 @@ -22,9 +22,9 @@ pow(2, 3) = 8 pow(2, 4) = 16 ``` -There are two ways to implement it. +这里有两种思路来实现它。 -1. Iterative thinking: the `for` loop: +1. 迭代式思维: 使用 `for` 循环: ```js run function pow(x, n) { @@ -41,7 +41,7 @@ There are two ways to implement it. alert( pow(2, 3) ); // 8 ``` -2. Recursive thinking: simplify the task and call self: +2. 递归式思维: 简化任务,调用自身: ```js run function pow(x, n) { @@ -55,9 +55,9 @@ There are two ways to implement it. alert( pow(2, 3) ); // 8 ``` -Please note how the recursive variant is fundamentally different. +请注意一下递归的形式有个根本的不同。 -When `pow(x, n)` is called, the execution splits into two branches: +当 `pow(x, n)` 被调用, 执行过程分成两个分支: ```js if n==1 = x @@ -67,27 +67,27 @@ pow(x, n) = else = x * pow(x, n - 1) ``` -1. If `n==1`, then everything is trivial. It is called *the base* of recursion, because it immediately produces the obvious result: `pow(x, 1)` equals `x`. -2. Otherwise, we can represent `pow(x, n)` as `x * pow(x, n-1)`. In maths, one would write xn = x * xn-1. This is called *a recursive step*: we transform the task into a simpler action (multiplication by `x`) and a simpler call of the same task (`pow` with lower `n`). Next steps simplify it further and further untill `n` reaches `1`. +1. 当 `n==1`时, 所有东西都不重要。此时被称作递归的 *初步*, 因为函数立即产生一个明显的结果,那就是: `pow(x, 1)` 等于 `x`。 +2. 否则, 我们可以将 `pow(x, n)` 用 `x * pow(x, n-1)`来表示。 在数学中可以写成这样的公式: xn = x * xn-1。 这被称为 *一个递归步骤*: 我们将任务转变为一个简单的动作(乘以 `x`)以及一个对相同任务的、更简单的调用(求小一点的 `n` 的次方 `pow` )。 下一步继续简化直至 `n` 等于 `1`。 -We can also say that `pow` *recursively calls itself* till `n == 1`. +我们也可以称为: `pow` *递归地调用自身* 直到 `n == 1`. ![recursive diagram of pow](recursion-pow.png) -For example, to calculate `pow(2, 4)` the recursive variant does these steps: +例如,计算 `pow(2, 4)` 时,递归方法会按照如下步骤进行计算: 1. `pow(2, 4) = 2 * pow(2, 3)` 2. `pow(2, 3) = 2 * pow(2, 2)` 3. `pow(2, 2) = 2 * pow(2, 1)` 4. `pow(2, 1) = 2` -So, the recursion reduces a function call to a simpler one, and then -- to even more simpler, and so on, until the result becomes obvious. +所以,递归将一个函数调用复杂度降低,持续进行进一步简单的调用,直到求得最终的结果。 -````smart header="Recursion is usually shorter" -A recursive solution is usually shorter than an iterative one. +````smart header="递归通常更短" +一个递归式方案通常都比迭代式方案更短。 -Here we can rewrite the same using the ternary `?` operator instead of `if` to make `pow(x, n)` more terse and still very readable: +这里我们利用三元操作符 `?` 而不是 `if` 来改写 `pow(x, n)` ,使其更加简洁并保持很好的可读性: ```js run function pow(x, n) { @@ -96,45 +96,45 @@ function pow(x, n) { ``` ```` -The maximal number of nested calls (including the first one) is called *recursion depth*. In our case, it will be exactly `n`. +潜逃调用的最多次数(包括第一次调用)被称为 *递归深度*. 此例中,递归深度等于 `n`。 -The maximal recursion depth is limited by JavaScript engine. We can make sure about 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases. +最大递归深度受限于 JavaScript 引擎。10000是一个可以基本保证的深度,有些引擎允许更深,但10000超过了大多数引擎允许的深度。有些自动优化的方法可以缓解此问题 ("tail calls optimizations"),但他们还没有得到大范围的支持,且很多时候只能支持简单的使用清醒。 -That limits the application of recursion, but it still remains very wide. There are many tasks where recursive way of thinking gives simpler code, easier to maintain. +虽然这限制了递归的应用场合,但它还是有着广泛的应用场合。有很多任务,使用递归方式思考的话,可以用更简单的代码来实现,从而更易于维护。 ## The execution stack -Now let's examine how recursive calls work. For that we'll look under the hood of functions. +我们通过进一步观察函数,再来审视一下递归调用的工作过程。 -The information about a function run is stored in its *execution context*. +函数运行有关的信息是储存在 *执行上下文* 中。 -The [execution context](https://tc39.github.io/ecma262/#sec-execution-contexts) is an internal data structure that contains details about the execution of a function: where the control flow is now, the current variables, the value of `this` (we don't use it here) and few other internal details. +[执行上下文](https://tc39.github.io/ecma262/#sec-execution-contexts) 用来保存一个函数在执行过程中细节的数据结构: 当前的控制流在哪里, 当前的变量,`this` 当前的值(我们在这还没有用到)以及其他的一些内部细节。 -One function call has exactly one execution context associated with it. +一个函数调用一定有一个执行上下文对应着。 -When a function makes a nested call, the following happens: +当一个函数进行嵌套调用时,以下会发生: -- The current function is paused. -- The execution context associated with it is remembered in a special data structure called *execution context stack*. -- The nested call executes. -- After it ends, the old execution context is retrieved from the stack, and the outer function is resumed from where it stopped. +- 当前函数会暂停。 +- 与之对应的执行上下文会保存在一个叫 *执行上下文堆栈* 的特殊数据结构中。 +- 嵌套的调用开始执行 +- 当嵌套调用结束后,老的执行上下文会被从堆栈中取出来,这样原来从外面调用的函数就可以从暂停的地方开始继续执行。 -Let's see what happens during the `pow(2, 3)` call. +让我们看看 `pow(2, 3)` 调用过程中发生了什么。 ### pow(2, 3) -In the beginning of the call `pow(2, 3)` the execution context will store variables: `x = 2, n = 3`, the execution flow is at line `1` of the function. +在 `pow(2, 3)` 调用刚发出时,执行上下文会存储变量: `x = 2, n = 3`,执行流现在是在函数的第 `1` 行。 -We can sketch it as: +这个过程草图大致是:
  • - Context: { x: 2, n: 3, at line 1 } + 上下文: { 第一行,x: 2, n: 3 } pow(2, 3)
-That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`: +函数开始执行后,条件 `n == 1` 的结果是“否”,因此执行流前行到 `if` 的第二个分支: ```js run function pow(x, n) { @@ -151,70 +151,70 @@ alert( pow(2, 3) ); ``` -The variables are same, but the line changes, so the context is now: +变量还是相同的变量,但行变了,所以上下文变成了:
  • - Context: { x: 2, n: 3, at line 5 } + 上下文: { 第5行,x: 2, n: 3 } pow(2, 3)
-To calculate `x*pow(x, n-1)`, we need to make a subcall of `pow` with new arguments `pow(2, 2)`. +为了计算 `x*pow(x, n-1)`,我们必须使用新的参数 `pow(2,2)` 来执行一个 `pow` 的子调用。 ### pow(2, 2) -To do a nested call, JavaScript remembers the current execution context in the *execution context stack*. +JavaScript 会在 *执行上下文堆栈* 中记住当前执行的上下文,以便进行一个嵌套调用。 -Here we call the same function `pow`, but it absolutely doesn't matter. The process is the same for all functions: +这里我们调用相同的函数 `pow`,但这不是重点,所有的函数的执行过程都是相同的: -1. The current context is "remembered" on top of the stack. -2. The new context is created for the subcall. -3. When the subcall is finished -- the previous context is popped from the stack, and its execution continues. +1. 当前上下文被“记忆”在堆栈的顶端。 +2. 与子调用相关的上下文会被创建。 +3. 当子调用结束后,前面的上下文会被从堆栈中弹出,从而可以继续执行。 -Here's the context stack when we entered the subcall `pow(2, 2)`: +以下是子调用 `pow(2, 2)` 发生时执行上下文堆栈的情况:
  • - Context: { x: 2, n: 2, at line 1 } + 上下文: { 第1行,x: 2, n: 2 } pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + 上下文: { 第5行, x: 2, n: 3 } pow(2, 3)
-The new current execution context is on top (and bold), and previous remembered contexts are below. +当前的执行上下文是在堆栈的顶端(粗体),前面记住的上下文在下面。 -When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. Here in the picture we use the word "line", but of course it's more precise. +当自调用完成后,恢复前面的上下文很容易,因为它保存了所有的变量值以及停止时所在的代码行。此此处我们使用了“行”这个字但实际上图中题意表达得更精确。 ### pow(2, 1) -The process repeats: a new subcall is made at line `5`, now with arguments `x=2`, `n=1`. +上述过程不停地重复: 第5行会发出一个新的子调用,此时使用变量值 `x=2`, `n=1`。 -A new execution context is created, the previous one is pushed on top of the stack: +一个新的执行上下文会被创建,前面的上下文会被压到堆栈的顶上。:
  • - Context: { x: 2, n: 1, at line 1 } + 上下文: { 第1行, x: 2, n: 1 } pow(2, 1)
  • - Context: { x: 2, n: 2, at line 5 } + 上下文: { 第5行, x: 2, n: 2 } pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + 上下文: { 第5行, x: 2, n: 3} pow(2, 3)
-There are 2 old contexts now and 1 currently running for `pow(2, 1)`. +现在有两个老的上下文以及一个针对 `pow(2, 1)`的新上下文。 ### The exit -During the execution of `pow(2, 1)`, unlike before, the condition `n == 1` is truthy, so the first branch of `if` works: +执行 `pow(2, 1)` 时,与前面的不一样,条件 `n == 1` 判断为“真”,所以 `if` 的第一个分支开始工作: ```js function pow(x, n) { @@ -228,42 +228,42 @@ function pow(x, n) { } ``` -There are no more nested calls, so the function finishes, returning `2`. +后续再无嵌套调用,函数结束,返回 `2`。 -As the function finishes, its execution context is not needed any more, so it's removed from the memory. The previous one is restored off the top of the stack: +当函数结束,关联的执行上下文就不再需要,所以会被从内存中移除,前面的执行上下文会被恢复到堆栈的顶上:
  • - Context: { x: 2, n: 2, at line 5 } + 上下文: { 第5行, x: 2, n: 2} pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + 上下文: { 第5行, x: 2, n: 3} pow(2, 3)
-The execution of `pow(2, 2)` is resumed. It has the result of the subcall `pow(2, 1)`, so it also can finish the evaluation of `x * pow(x, n-1)`, returning `4`. +`pow(2, 2)`的执行得以恢复。 它有自调用 `pow(2, 1)`所带来的结果,因此 `x * pow(x, n-1)`也就可以求出结果,返回 `4`。 -Then the previous context is restored: +接下来前面的上下文又可以得到恢复:
  • - Context: { x: 2, n: 3, at line 5 } + 上下文: { 第5行, x: 2, n: 3} pow(2, 3)
-When it finishes, we have a result of `pow(2, 3) = 8`. +当它也执行结束,我们就可以得出: `pow(2, 3) = 8`。 -The recursion depth in this case was: **3**. +此处的递归深度是: **3**. -As we can see from the illustrations above, recursion depth equals the maximal number of context in the stack. +从上述示意可以看出,递归深度等于堆栈中上下文的最大值。 -Note the memory requirements. Contexts take memory. In our case, raising to the power of `n` actually requires the memory for `n` contexts, for all lower values of `n`. +要注意一下对内存的需求。上下文会消耗内存。在我们这个例子中,求 `n` 次方实际上需要消耗 `n`个上下文的所占用的内存,以便支持所有小于 `n`的值计算。 -A loop-based algorithm is more memory-saving: +基于循环的算法可以节省很多内存: ```js function pow(x, n) { @@ -277,19 +277,19 @@ function pow(x, n) { } ``` -The iterative `pow` uses a single context changing `i` and `result` in the process. Its memory requirements are small, fixed and do not depend on `n`. +迭代式的 `pow`在执行过程中需要不停变化 `i` 以及 `result`,但只需要一个上下文。所需要的内存是较少且是固定的,与 `n`值大小无关。 -**Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** +**所有的递归都可以改写成循环。循环的方式通常都有更高的效率。** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +...但有时候这种改写的工作量相当大,特别是函数根据条件有着不同的递归子调用或者分支条件错综复杂。此时的优化可能没有必要或者说不值得花费那么多的功夫。 -Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. +递归带来更短的代码并且易于理解与维护。并不是所有的地方都需要优化,更多的时候我们是需要更好的代码,这就是为何递归得到应用的原因。 -## Recursive traversals +## 递归shi遍历 -Another great application of the recursion is a recursive traversal. +递归另外一个伟大的应用场合是递归式遍历 -Imagine, we have an company. The staff structure can be presented as an object: +想象一下我们有一家公司,人员架构可以用一个对象来表示: ```js let company = { @@ -318,30 +318,30 @@ let company = { }; ``` -In other words, a company has departments. +换言之,公司下面有部门。 -- A department may have an array of staff. For instance, `sales` department has 2 employees: John and Alice. -- Or a department may split into subdepartments, like `development` has two branches: `sites` and `internals`. Each of them has the own staff. -- It is also possible that when a subdepartment grows, it divides into subsubdepartments (or teams). +- 一个部门中可能有人员数组。例如: `sales` (销售)部门有两个员工: John 与 Alice。 +- 或者一个部门可能包含有两个子部门,例如 `development`(开发)部门下有两个分支:`site`与`internal`。每个分支都有各自的员工。 +- 当一个子部门增长时,它可能被分成子子部门(或者 `team`,即:团队)。 - For instance, the `sites` department in the future may be split into teams for `siteA` and `siteB`. And they, potentially, can split even more. That's not on the picture, just something to have in mind. + 例如:`sites` 部门未来可能被分成两个团队,分别是: `siteA` 与 `siteB`。他们未来可能被进一步的细分。这些在图中没有表示出来,但你可以在脑中想象一下。 -Now let's say we want a function to get the sum of all salaries. How can we do that? +现在假如说我们需要一个函数,用来获取所有的薪水总数,那我们该如何做呢? -An iterative approach is not easy, because the structure is not simple. The first idea may be to make a `for` loop over `company` with nested subloop over 1st level departments. But then we need more nested subloops to iterate over the staff in 2nd level departments like `sites`. ...And then another subloop inside those for 3rd level departments that might appear in the future? Should we stop on level 3 or make 4 levels of loops? If we put 3-4 nested subloops in the code to traverse a single object, it becomes rather ugly. +迭代式的方案此时就不容易实现,因为人员结构不简单。迭代式方法不容易实现,因为没有简单的结构。首先能想到的主意,或许是用 `for`循环遍历整个 `company`,然后用子循环来遍历所有的1级部门。但我们马上就需要更多的子循环遍历像 `sites`这样的2级部门。那接下来的3级子部门,我们是否需要更多的子循环呢?我们应该停在3级还是需要继续到4级?同时,我们将遍历3-4层的子循环的代码都放在一个单一对象内部,那代码本身也够丑陋的。 -Let's try recursion. +现在就是试试递归方式的时候了。 -As we can see, when our function gets a department to sum, there are two possible cases: +我们可以看到,当函数对一个部门进行求和时,会碰到两种情况: -1. Either it's a "simple" department with an *array of people* -- then we can sum the salaries in a simple loop. -2. Or it's *an object with `N` subdepartments* -- then we can make `N` recursive calls to get the sum for each of the subdeps and combine the results. +1. 要么这个部门很简单,只有 *人员数组*。* -- 那么我们简单地用循环来累加薪水。 +2. 要么这个部门是 *一个带有 `N` 个子部门的对象* -- 那么我们可以递归地调用 `N`次函数以求得各个子部门的薪水之和,然后再合并到一起。 -The (1) is the base of recursion, the trivial case. +上面所述的 (1) 就是递归的基础,一个不复杂的清醒。 -The (2) is the recursive step. A complex task is split into subtasks for smaller departments. They may in turn split again, but sooner or later the split will finish at (1). +而 (2) 就是递归的步骤. 一个复杂的任务被分解成针对各个下一级部门的子任务。子任务会被再次分解,但很快这些子任务都会到达 (1)的情形时截止。 -The algorithm is probably even easier to read from the code: +通过代码来了解这个算法可能更简单: ```js run @@ -371,62 +371,62 @@ function sumSalaries(department) { alert(sumSalaries(company)); // 6700 ``` -The code is short and easy to understand (hopefully?). That's the power of recursion. It also works for any level of subdepartment nesting. +上述代码短小且易于理解(希望如此),这就是递归的威力。它适应于任何层级子部门的嵌套情形。 -Here's the diagram of calls: +以下是调用的示意图: ![recursive salaries](recursive-salaries.png) -We can easily see the principle: for an object `{...}` subcalls are made, while arrays `[...]` are the "leaves" of the recursion tree, they give immediate result. +我们很容易发现原则是:针对每个 `{...}` 发出的自调用,数组 `[...]` 是第归树上的 "叶子" 他们总是能够马上给出一个结果。 -Note that the code uses smart features that we've covered before: +注意一下这里的代码使用了我们前面介绍过的一些聪明技术: -- Method `arr.reduce` explained in the chapter to get the sum of the array. -- Loop `for(val of Object.values(obj))` to iterate over object values: `Object.values` returns an array of them. +- `arr.reduce` 方法在章节 中有介绍,用来获取一个数组的和。 +- 循环 `for(val of Object.values(obj))` 可以用来遍历每个对象的值, `Object.values` 以数组的方式返回他们的值。 -## Recursive structures +## 递归式结构 -A recursive (recursively-defined) data structure is a structure that replicates itself in parts. +一个递归式的数据结构(递归式地定义)是指部分重复自身的结构。 -We've just seen it in the example of a company structure above. +我们刚刚看过了上面一个公司的结构。 -A company *department* is: -- Either an array of people. -- Or an object with *departments*. +一个公司的*部门*是: +- 要么一个人员数组 +- 要么是包含了很多*部门*的对象. -For web-developers there are much better known examples: HTML and XML documents. +对于一个Web开发人员来说,HTML与 XML 文档可能是更好理解的一个例子。 -In the HTML document, an *HTML-tag* may contain a list of: -- Text pieces. -- HTML-comments. -- Other *HTML-tags* (that in turn may contain text pieces/comments or other tags etc). +在一个HTML文档当中,一个*HTML标签* 可能包含了一个如下列表: +- 文本部分。 +- HTML注释。 +- 其他的 *HTML标签* (也即可以包含文本、HTML注释或者标签等等)。 -That's once again a recursive definition. +这就又是一个递归式的定义。 -For better understanding, we'll cover one more recursive structure named "Linked list" that might be a better alternative for arrays in some cases. +为了更好地理解,我们将讨论另一个递归式的结构,名叫: "Linked list 链式列表" ,这个在很多情况下是比数组更好的一个替代。 -### Linked list +### 链式列表 -Imagine, we want to store an ordered list of objects. +想象一下,我们需要存储一个有序的对象列表。 -The natural choice would be an array: +数组可能是一个自然的选择: ```js let arr = [obj1, obj2, obj3]; ``` -...But there's a problem with arrays. The "delete element" and "insert element" operations are expensive. For instance, `arr.unshift(obj)` operation has to renumber all elements to make room for a new `obj`, and if the array is big, it takes time. Same with `arr.shift()`. +...但数组有一个问题,“删除元素“与”插入元素“操作成本非常高。例如: `arr.unshift(obj)` 操作会为了腾出空间一个新的 `obj`而不得不重新编号所有的元素,当数组很大时,需要很多时间。 `arr.shift()`操作也有类似问题。 -The only structural modifications that do not require mass-renumbering are those that operate with the end of array: `arr.push/pop`. So an array can be quite slow for big queues. +数组结构操作中唯一不需要大量重新编号的是在数组结尾处操作: `arr.push/pop`。所以,对于一个大的队列来说,数组操作起来很慢。 -Alternatively, if we really need fast insertion/deletion, we can choose another data structure called a [linked list](https://en.wikipedia.org/wiki/Linked_list). +如果一定需要快速的插入、删除,那么可以选择另外一种叫做链式列表的数据结构做替代。[链式列表](https://en.wikipedia.org/wiki/Linked_list). -The *linked list element* is recursively defined as an object with: -- `value`. -- `next` property referencing the next *linked list element* or `null` if that's the end. +*链式列表中的元素* 是用递归方法定义的对象: +- `value` 值. +- `next` (下一个)属性指向下一个 *链式列表元素*,若是空 `null` 则表示这是尾部。 -For instance: +例如: ```js let list = { @@ -444,11 +444,11 @@ let list = { }; ``` -Graphical representation of the list: +上述列表的可以用图形表示为: ![linked list](linked-list.png) -An alternative code for creation: +可以用下面的代码构建上述数据: ```js no-beautify let list = { value: 1 }; @@ -457,9 +457,9 @@ list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; ``` -Here we can even more clearer see that there are multiple objects, each one has the `value` and `next` pointing to the neighbour. The `list` variable is the first object in the chain, so following `next` pointers from it we can reach any element. +这里我们可以更加清晰地看到这里有多个对象,每个对象都有 `value` 以及指向邻居的 `next`。 `list` 变量指向这个链条中的第一个对象,因此,顺着 `next` 指针我们找到任何对象。 -The list can be easily split into multiple parts and later joined back: +列表可以很容地被分成多个部分,也可以很容易合并回来: ```js let secondList = list.next.next; @@ -468,15 +468,15 @@ list.next.next = null; ![linked list split](linked-list-split.png) -To join: +合并: ```js list.next.next = secondList; ``` -And surely we can insert or remove items in any place. +当然也可以在任何位置插入或者删除一个元素。 -For instance, to prepend a new value, we need to update the head of the list: +例如,我们要前置一个值,我们只需要更新列表的头部: ```js let list = { value: 1 }; @@ -492,7 +492,7 @@ list = { value: "new item", next: list }; ![linked list](linked-list-0.png) -To remove a value from the middle, change `next` of the previous one: +当需要删除中间的一个值时,可以更改 `next` 指向下一个: ```js list.next = list.next.next; @@ -500,35 +500,35 @@ list.next = list.next.next; ![linked list](linked-list-remove-1.png) -We made `list.next` jump over `1` to value `2`. The value `1` is now excluded from the chain. If it's not stored anywhere else, it will be automatically removed from the memory. +我们让 `list.next` 跳过了 `1` 直接指向 `2`。 `1`这个值就从链条中被排除出去。如果没有别的地方引用或者存储的话,那么它将会被从内存中自动回收。 -Unlike arrays, there's no mass-renumbering, we can easily rearrange elements. +不像数组,这里没有大批量的重新编号,我们可以很容易地重新安排元素。 -Naturally, lists are not always better than arrays. Otherwise everyone would use only lists. +自然地,列表并不是总是比数组表现好。否则,每个人都只用列表了。 -The main drawback is that we can't easily access an element by its number. In an array that's easy: `arr[n]` is a direct reference. But in the list we need to start from the first item and go `next` `N` times to get the Nth element. +我们不能通过编号直接访问一个元素是列表的主要缺陷,但对数组来说却很容易: `arr[n]` 就是直接访问元素的方法。但如果是一个列表的话,我们必须从列表的第一个元素开始,通过 `N`次的 `next`找到第N个元素。 -...But we don't always need such operations. For instance, when we need a queue or even a [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- the ordered structure that must allow very fast adding/removing elements from both ends. +...但我们并不总是需要这样的操作。比方说,我们可以使用一个队列真是一个[deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- 此类有序的结构,便于快速地从两端增加、删除元素。 -Sometimes it's worth to add another variable named `tail` to track the last element of the list (and update it when adding/removing elements from the end). For large sets of elements the speed difference versus arrays is huge. +有时候值得增加一个 `tail`变量来跟踪列表中的最后一个元素,当然从尾部增加、删除元素时需要更新它。当元素很多时,使用列表与使用数组的速度差别还是很大的。 -## Summary +## 总结 -Terms: -- *Recursion* is a programming term that means a "self-calling" function. Such functions can be used to solve certain tasks in elegant ways. +术语: +- *递归* 时一个编程术语,表示一个"调用自身"的函数。这种函数可以优雅地解决一些特定的问题。 - When a function calls itself, that's called a *recursion step*. The *basis* of recursion is function arguments that make the task so simple that the function does not make further calls. + 当一个函数调用自身时,这叫着一个 *递归步骤*。 递归的*基础*是指简单到函数不用再做更多一次调用的函数参数。 -- A [recursively-defined](https://en.wikipedia.org/wiki/Recursive_data_type) data structure is a data structure that can be defined using itself. +- 一个递归式定义的[recursively-defined](https://en.wikipedia.org/wiki/Recursive_data_type) 数据结构是指可以使用自身来定义一种数据结构。 - For instance, the linked list can be defined as a data structure consisting of an object referencing a list (or null). + 例如:链式列表可以定义为指向列表或者空的对象构成的列表。 ```js list = { value, next -> list } ``` - Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches. + 这个章节里面所谈到的HTML元素树或者部门树都是一种递归式的定义,他们有分支,且每个分支可以有更深一层的分支。 - Recursive functions can be used to walk them as we've seen in the `sumSalary` example. + 如果求薪水和`sumSalary` 例子所示,递归函数可以用来遍历他们。 -Any recursive function can be rewritten into an iterative one. And that's sometimes required to optimize stuff. But for many tasks a recursive solution is fast enough and easier to write and support. +所有的递归式函数都可以被改写成迭代式的,特别是在要求做优化时。但递归式方案对于很多解决方案来说已经够快,并且更加容易编写与维护。