记忆提供更少的表现,花费更多的时间
我正在FreecodeCamp解决Euler项目。解决问题14时,我会使用递归,并尝试使用记忆来提高性能。但是,如果没有记忆,则需要更少的时间来执行,并且通过记忆需要更多的时间。备忘录实施是正确的吗?此代码有什么问题?有人可以解释吗?
//Memoized version of the longestCollatzSequence project euler
let t1 = Date.now();
// function recurseWrapper(n) {
// let count = 0;
// let memo = {}
// function recurseCollatzSequence(n) {
// if (n in memo) {
// return memo[n];
// } else {
// if (n === 1) {
// return;
// } else if (n % 2 === 0) {
// count++;
// memo[n / 2] = recurseCollatzSequence((n / 2))
// } else {
// count++;
// memo[(3 * n) + 1] = recurseCollatzSequence(((3 * n) + 1))
// }
// return count
// }
// }
// return recurseCollatzSequence(n);
// }
//Without memoization (better performance)
function recurseWrapper(n) {
let count = 0;
function recurseCollatzSequence(n) {
if (n === 1) {
return;
} else if (n % 2 === 0) {
count++;
recurseCollatzSequence((n / 2))
} else {
count++;
recurseCollatzSequence(((3 * n) + 1))
}
return count
}
return recurseCollatzSequence(n);
}
function longestCollatzSequence(n) {
let max = 0;
let startNum = 0;
for (let i = n; i > 1; i--) {
let changeMax = recurseWrapper(i)
if (changeMax > max) {
max = changeMax;
startNum = i;
}
}
return startNum;
}
console.log(longestCollatzSequence(54512))
let t2 = Date.now() - t1;
console.log(`time taken by first instruction ${t2}`);
console.log(longestCollatzSequence(900000));
let t3 = Date.now() - t1 - t2
console.log(`time taken by second instruction ${t3}`);
let t4 = Date.now() - t1 - t2 - t3
console.log(longestCollatzSequence(1000000))
console.log(`time taken by third instruction ${t4}`);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
从我对Collatz猜想的有限理解,从数字
n
开始时,使用您执行的所有操作,您再也不会看到相同的数字直到达到1(否则您最终会出现在无限循环中)。因此,您的备忘录
对象将始终容纳永远不会匹配当前号码n
的唯一键,因此如果(n中的n中的n中)永远不会是正确的。
实际上,您似乎想在循环中记忆结果
recursewrapper()
,以便可以防止已经看到的计算结果。目前,您尚未这样做,因为您每次调用recurseWrapper()
时,要创建一个新的Memo
对象,以删除所有记忆的值。取而代之的是,您可以返回通过备忘录
对象关闭的内部辅助递归包装器功能,以便将一个备忘对象保留在递归包装器功能的所有调用中。但是,即使如此,我们仍然会面临问题,因为如何计算。例如,如果您调用
recurseWrapper(n)
,并且需要10个迭代才能调用recurseCollatzSequence(k)
,则返回的recurseCollatzSequence(k)将为
count
的方式,以便它是 pure 相同的结果。10
加上计算k
的Collatz序列所需的任何数字。如果我们记住这个数字,我们可能会面临问题。如果我们再次调用recurseWrapper
在另一个号码m
,recurseWrapper(m)
,这次需要20个迭代才能到达相同的呼叫recurseCollatzSequence(k)
,我们将使用k
的记忆价值。但是,此值对从n
到k
进行的10
还有一个额外的计数,而不仅仅是所花费的计数从k
到1
。结果,我们需要更改您的递归函数中计算正如文森特(Vincent)也在a ,您应该记住当前号码,即:
备忘录[n]
,而不是您将要计算Collatz计数的数字(重新浏览时进行了记忆):相比之下,以下显示没有备忘录的时间:
From my limited understanding of the collatz conjecture, when starting at a number
n
, with all of the operations that you do, you should never see the same number again until you reach 1 (otherwise you would end up in an infinite loop). So yourmemo
object will always hold unique keys that will never match the current numbern
, soif (n in memo)
will never be true.It actually seems that you want to memoize your results for further calls
recurseWrapper()
within your loop, so that you can prevent computing results you've already seen. At the moment you are not doing that, as you are creating a newmemo
object each time you callrecurseWrapper()
, removing all of the memoized values. You can instead return your inner auxiliary recursive wrapper function that closes over thememo
object so that you keep the one memo object for all calls of the recursive wrapper function.But even then we will still face issues, because of how the
count
is being calculated. For example, if you callrecurseWrapper(n)
and it takes 10 iterations to callrecurseCollatzSequence(k)
, the returned count ofrecurseCollatzSequence(k)
is going to be10
plus whatever number it takes to compute the Collatz sequence fork
. If we memoize this number, we can face issues. If we again callrecurseWrapper
on another numberm
,recurseWrapper(m)
, and this time it takes 20 iterations to get to the same call ofrecurseCollatzSequence(k)
, we will use our memoized value fork
. But this value holds an additional count for the10
that it took to get fromn
tok
, and not just the count that it took to get fromk
to1
. As a result, we need to change how you're computingcount
in your recursive function so that it is pure, so that calling a function with the same arguments always produces the same result.As Vincent also points out in a comment, you should be memoizing the current number, ie:
memo[n]
and not the number you're about to compute the Collatz count for (that memoization is done when you recurse):In comparison, the below shows times without memo:
这是该解决方案的简化版本,在纪念和递归周围具有最小的噪声。
而且,如果您愿意接受一些高尔夫球,这仍然是第一个功能的较短版本。
之所以重要的原因与我们的想法有关。只要代码自然而然地读取,写作和思考它需要多长时间才与它的时间直接相关。 (在许多地方都发现这在多种语言中都是真实的。我知道当然有。)因此,学会对代码进行更有效的思考将使您更快地编写。
这就是为什么在不谈论所使用的技术的情况下学习如何使用技术会使您在这种技术方面更好的原因。
Here is a streamlined version of the solution with minimal noise around memoization and recursion.
And if you're willing to accept a bit of golf, here is a shorter version still of the first function.
The reason why this matters has to do with how we think. As long as code reads naturally to us, how long it takes to write and think about it is directly correlated with how long it is. (This has been found to be true across a variety of languages in many places. I know that Software Estimation: Demystifying the Black Art certainly has it.) Therefore learning to think more efficiently about code will make it faster for you to write.
And that is why learning how to use techniques without talking about the technique you're using makes you better at that technique.
花了一些时间弄清楚,我找到了一个工作解决方案。
以下代码提高了时间复杂性。谢谢大家帮助我。
After spending some time figuring out I found a working solution.
The code below improved time complexity. Thanks all for helping me.