我试图理解这个递归代码并且已经卡住了

时间:2017-01-18 23:50:34

标签: javascript recursion

我认为我已经掌握了Javascript中的递归,但我很欣赏一些特定的递归代码,我会在一本书中阅读

根据我的理解,下面的代码正在执行几个步骤,如果您能够纠正我的理解中的任何错误,我会解释,我会非常感激:

  • findSolution功能正在为您寻找解决方案 加5或乘以3得到24

  • 函数find是递归发生的地方 解决方案,声明if (start == target)是告诉我的 递归结束时找到解决方案并返回历史记录 怎么会发生这种情况

  • 第8-9行的return语句以(1 + 5)开头,等于6, 所以然后它开始回到顶部继续通过if 不满足的陈述然后进入return语句 再次这次(6 + 5)等于11

  • 它将继续执行此操作,直到满足其中一个if语句。什么时候 start高于目标函数然后进入the ||的另一面声明并以(1 * 3)开头 历史等同于"(1 * 3)"

我不确定的是为什么函数继续通过第一部分在下一次迭代中将(5 *)加到5(1 * 3),函数如何知道添加5然后在下一次迭代中乘以3?为什么它不会继续添加5然后再这样做,直到它太大并返回null?

function findSolution(target) {
  function find(start, history) {
    if (start == target)
      return history;
    else if (start > target)
      return null;
    else
      return find(start + 5, "(" + history + " + 5)") ||
             find(start * 3, "(" + history + " * 3)");
  }
  return find(1, "1");
}

console.log(findSolution(24));
// → (((1 * 3) + 5) * 3)

4 个答案:

答案 0 :(得分:1)

它确实经历了你提到的每一种可能的情况。它确实继续添加5,直到它太大并返回null(在这种情况下为find(start + 5, "(" + history + " + 5)") === null (false),因此结果将来自另一个分支。

这很难解释为什么你需要了解执行堆栈或者可能会绘制一个执行树。 让我知道我该如何帮助

答案 1 :(得分:1)

也许这会让事情更清楚。

我刚刚在depth - 函数中添加了find - 参数来确定递归深度,并在console.log()中记录所有递归调用。



function findSolution(target) {
    //added a depth-property to show the recursion better
    function find(start, history, depth) {
        //simply log all calls in order
        console.log("%s%d == %s", "|  ".repeat(depth), start, history);

        if (start == target)
            return history;
        else if (start > target)
            return null;
        else
            return find(start + 5, "(" + history + " + 5)", depth+1) ||
                   find(start * 3, "(" + history + " * 3)", depth+1);
    }
    return find(1, "1", 0);
}

console.log(findSolution(24));




答案 2 :(得分:0)

以下是您的代码版本,它将为您打印一个执行树,以便您可以跟随该算法的所有不同排列,实际上,尝试:

function findSolution(target) {
  function find(start, history, trace) {
    trace.push(history)
    if (start == target) {
      console.log(trace.join(' => ') + ' => ' + start + ' == ' + target);
      return history;
    }
    if (start > target) {
      console.log(trace.join(' => ') + ' => ' + start + ' > ' + target);
      return null;
    }
    return find(start + 5, "(" + history + " + 5)", [].slice.call(trace)) ||
           find(start * 3, "(" + history + " * 3)", [].slice.call(trace));
  }
  return find(1, "1", []);
}
findSolution(24)

这是执行树:

1 => (1 + 5) => ((1 + 5) + 5) => (((1 + 5) + 5) + 5) => ((((1 + 5) + 5) + 5) + 5) => (((((1 + 5) + 5) + 5) + 5) + 5) => 26 > 24
1 => (1 + 5) => ((1 + 5) + 5) => (((1 + 5) + 5) + 5) => ((((1 + 5) + 5) + 5) + 5) => (((((1 + 5) + 5) + 5) + 5) * 3) => 63 > 24
1 => (1 + 5) => ((1 + 5) + 5) => (((1 + 5) + 5) + 5) => ((((1 + 5) + 5) + 5) * 3) => 48 > 24
1 => (1 + 5) => ((1 + 5) + 5) => (((1 + 5) + 5) * 3) => 33 > 24
1 => (1 + 5) => ((1 + 5) * 3) => (((1 + 5) * 3) + 5) => ((((1 + 5) * 3) + 5) + 5) => 28 > 24
1 => (1 + 5) => ((1 + 5) * 3) => (((1 + 5) * 3) + 5) => ((((1 + 5) * 3) + 5) * 3) => 69 > 24
1 => (1 + 5) => ((1 + 5) * 3) => (((1 + 5) * 3) * 3) => 54 > 24
1 => (1 * 3) => ((1 * 3) + 5) => (((1 * 3) + 5) + 5) => ((((1 * 3) + 5) + 5) + 5) => (((((1 * 3) + 5) + 5) + 5) + 5) => ((((((1 * 3) + 5) + 5) + 5) + 5) + 5) => 28 > 24
1 => (1 * 3) => ((1 * 3) + 5) => (((1 * 3) + 5) + 5) => ((((1 * 3) + 5) + 5) + 5) => (((((1 * 3) + 5) + 5) + 5) + 5) => ((((((1 * 3) + 5) + 5) + 5) + 5) * 3) => 69 > 24
1 => (1 * 3) => ((1 * 3) + 5) => (((1 * 3) + 5) + 5) => ((((1 * 3) + 5) + 5) + 5) => (((((1 * 3) + 5) + 5) + 5) * 3) => 54 > 24
1 => (1 * 3) => ((1 * 3) + 5) => (((1 * 3) + 5) + 5) => ((((1 * 3) + 5) + 5) * 3) => 39 > 24
1 => (1 * 3) => ((1 * 3) + 5) => (((1 * 3) + 5) * 3) => 24 == 24

答案 3 :(得分:0)

首先,函数find以1.内部查找:

开头
  • 如果我们拥有的东西与结果相同,那么我们就会回到那里的历史
  • 如果没有,我们会看到我们是否已经通过了目标(我们已经超过了目标,因此乘以3或添加5 s将无法使用它。
  • 最后,如果我们瞄准目标。然后我们采取到目前为止(历史,文字(字符串)和数字(结果))通过添加5开始两个分支,另一个乘以3。 / LI>

第一次发现的调用是1的结果和"1"的历史记录。

在第一个find内部测试(start == target)将失败,因为(1!= 24),第二个测试(start > target)也将失败,因为(1 <= 24 )。所以我们执行else内的内容。所以我们调用find会得到(1 + 5)的结果和("( 1 + 5)")的历史记录,然后检查我们是否有一个历史记录让我们到24(返回值不会是null),或者如果返回的值为null,则调用另一个查找结果为(1 * 3))和历史记录("( 1 * 3 "),我们检查我们是否得到了与null(正确答案)不同的返回值(没有答案)。

最好是由具有所有可能调用的树视图表示:

[1] -> [1 + 5] -> [6 + 5] -> [11 + 5] -> [16 + 5] -> [21 + 5] >> branch terminated (26 > 24)
   |          |          |           |            -> [21 * 3] >> branch terminated (63 > 24)
   |          |          |            -> [16 * 3] >> branch terminated (48 > 24)
   |          |           -> [11 * 3] >> branch terminated (33 > 24)
   |           -> [6 * 3] >> returns the right result (24 == 24)
   |
   |
   | We will never have the next branch because we already got a result.
    -> [1 * 3] -> [3 + 5] -> [8 + 5] -> [13 + 5] -> [18 + 5] -> [23 + 5] >> branch would have been terminated (28 > 24)
              |          |          |           |            -> [23 * 3] >> branch would have been terminated (69 > 24)
              |          |          |            -> [18 * 3] >> branch would have been terminated (54 > 24)
              |          |           -> [13 * 3] >> branch would have been terminated (39 > 24)
              |           -> [8 * 3] >> would have been a correct answer if we haven't already got one
               -> [3 * 3] -> [9 + 5] -> [14 + 5] -> [19 + 5] >> would have been a correct answer if we haven't already got one
                         |          |            -> [19 * 3] >> terminated ...
                         |           -> [14 * 3] >> terminated ...
                          -> [9 * 3] >> terminated ...

已终止的分支意味着条件(start > target)已满足,因此返回null给调用者将产生3种可能的结果:

  1. 如果来电者是另一个find且返回find的{​​{1}}是null中的第一个find,那么我们称之为{{1}分支。
  2. 如果来电者是另一个find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)")而且返回* 3的{​​{1}}是find中的第二个find,那么我们会返回null这将终止父赎罪(这个find)。
  3. 如果来电者是find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"),那么仅使用nullfind组合就无法实现目标。
  4. 返回正确答案的findSolution(换句话说,满足条件+ 5的{​​{1}})将阻止创建更多分支(它将返回非{{1}结果将由其调用者及其调用者返回...一直到* 3)所以如果我们在find中的第一个find,那么第二个将永远不会被执行。