ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

30-seconds-of-code:用 JavaScript 计算阶乘的迭代与递归实现指南

30-seconds-of-code:用 JavaScript 计算阶乘的迭代与递归实现指南 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载导读阶乘Factorial是数学与算法领域最基础的计算之一也是理解**递归recursion与迭代iteration**两种编程思路的经典入门案例。本文基于 30-seconds-of-code 仓库中的 factorial 代码片段系统讲解如何用 JavaScript 计算n的阶乘完整覆盖迭代实现、递归实现、边界处理、复杂度对比以及基于仓库源码的进阶优化思路。读完本文你将掌握两种可直接复制运行的阶乘实现并能在实际项目中根据数据规模正确选择方案。阶乘的数学定义与代码映射在数学中非负整数n的阶乘记作n!定义为所有小于或等于n的正整数的乘积n! n × (n-1) × (n-2) × ... × 2 × 1例如6! 6 × 5 × 4 × 3 × 2 × 1 720。特殊约定0! 1空积约定也是递归实现的基准情形之一。用代码表达这个定义有两种自然思路迭代Iterative用循环把2到n逐一乘进结果变量递归Recursive把n!分解为n × (n-1)!让函数调用自身直到到达基准情形。在 30-seconds-of-code 仓库中这一主题被组织为 js/recursion 集合 的成员snippetIds中包含js/s/factorial与 递归入门、斐波那契、最大公约数与最小公倍数 等文章共同构成一套完整的递归学习路径。迭代实现for 循环累积乘积原文档给出的迭代实现用for循环更新结果变量const factorial n { if (n 0) throw new TypeError(Negative numbers are not allowed!); let result 1; for (let i 2; i n; i) result * i; return result; }; factorial(6); // 720这段代码的核心逻辑可以拆解为三点入参校验n 0时直接抛出TypeError。阶乘定义域是非负整数负数的阶乘没有数学意义提前抛错可以避免循环条件失效i 2恒大于负数n循环永远不会执行最终错误地返回1。循环起点为2result初始化为1从i 2开始累乘。这样既天然覆盖了0! 1与1! 1循环体一次都不执行也避免了无意义的×1运算同时规避了0作为乘数会令结果恒为0的错误。单表达式循环体for (let i 2; i n; i) result * i;把乘法写在循环头部之后代码紧凑性能上等价于完整块写法。为什么不推荐用 reduce 改写原文档在注意事项中明确提示可以把for循环改写成Array.prototype.reduce()形式但不推荐因为它效率更低。改写后的等价形态大致如下const factorial n { if (n 0) throw new TypeError(Negative numbers are not allowed!); return Array.from({ length: n }, (_, i) i 1) .reduce((acc, x) acc * x, 1); };不推荐的理由很实际reduce版本需要先构造一个长度为n的数组额外内存分配再经历一次数组遍历额外开销而for循环只维护一个计数器变量无中间数组。虽然对常见的小n两者差异可以忽略但在批量计算或嵌入式场景中for循环始终是更省资源的写法。递归实现函数调用自身的优雅写法递归思路把阶乘定义改写成递推关系n! n × (n-1)!直至基准情形。原文档给出的实现const factorial n { if (n 0) throw new TypeError(Negative numbers are not allowed!); return n 1 ? 1 : n * factorial(n - 1); };执行factorial(5)时调用栈的展开过程是factorial(5) 5 * factorial(4) 5 * (4 * factorial(3)) 5 * (4 * (3 * factorial(2))) 5 * (4 * (3 * (2 * factorial(1)))) 5 * (4 * (3 * (2 * 1))) 120关键设计是基准情形base casen 1时直接返回1不再继续调用自身。正如仓库中的递归入门文章所述The base case breaks out of the recursion loop——如果没有基准情形函数将无限调用自身最终导致栈溢出stack overflow。注意这里基准条件写成n 1而非n 0是因为0! 1且1! 1合并判断让两行退化输入都直接返回代码更简洁。而在函数式编程入门中同一主题还有另一种等价写法以num 0为基准可作为对比参考const factorial num { if (num 0) return 1; return num * factorial(num - 1); };递归的代价函数调用开销递归实现虽然结构优雅、与数学定义一一对应但每次调用都要压入新的调用栈帧存在函数调用本身的开销。对阶乘这种一条线性递减链的计算而言n次递归调用意味着n层栈帧当n较大例如数万时可能触发栈溢出这也是原文档明确提示递归可能因函数调用开销而效率较低的原因。两种实现复杂度与可读性对比维度迭代实现递归实现时间复杂度O(n)单次循环O(n)但叠加函数调用开销空间复杂度O(1)仅一个result变量O(n)调用栈深度随n增长可读性直观贴近数学过程更优雅与数学定义n! n × (n-1)!直接对应栈溢出风险无有n较大时可能发生适用场景常规计算、性能敏感路径教学演示、递归思维训练、小规模输入关于时间复杂度的依据可以对照仓库中的 Big-O Cheat Sheet其中将O(n!)描述为阶乘级时间复杂度最差效率等级而本文两种实现的时间复杂度都是O(n)随输入线性增长n!只是计算结果的数值大小与算法本身的复杂度等级是两个概念——这是面试中常见的辨析点。边界情况与错误处理两种实现都应当处理以下边界输入n 0返回1数学约定0! 1迭代版本循环不执行、递归版本命中n 1基准均正确。n 1返回1逻辑同上。n 0抛出TypeError(Negative numbers are not allowed!)拒绝无数学定义的输入。非整数n本文实现未做显式校验若传入小数如2.5迭代版循环次数取决于i n的浮点比较结果、递归版则会因n - 1永远到不了 1而无限递归最终栈溢出。从源码结构看实际使用中如需健壮性可在校验处追加Number.isInteger(n)判断。n过大阶乘数值增长极快21!已超过 JavaScriptNumber的MAX_SAFE_INTEGER精度丢失此时应改用BigInt例如const factorial n { if (n 0) throw new TypeError(Negative numbers are not allowed!); let result 1n; for (let i 2n; i BigInt(n); i) result * i; return result; };进阶递归性能优化的三种方向递归是函数式编程的核心概念之一见仓库函数式编程入门但正如递归性能优化一文所言递归代码往往需要优化。针对阶乘可参考的优化方向有三类改用迭代将自顶向下分解改为自底向上累积即本文第一种实现。该文以斐波那契为例验证了这一思路——迭代方案无需缓存、无递归调用开销占用资源更少。记忆化Memoization用Map缓存已计算结果避免重复计算。记忆化入门指出其适用前提是同一函数在相同参数下被多次调用。对阶乘而言若程序中会反复计算不同规模的n!如组合数公式C(n, k) n! / (k! × (n-k)!)缓存中间结果能显著提速const factorialCache new Map([[0, 1], [1, 1]]); const factorial n { if (n 0) throw new TypeError(Negative numbers are not allowed!); if (factorialCache.has(n)) return factorialCache.get(n); const result n * factorial(n - 1); factorialCache.set(n, result); return result; };尾递归把累积结果作为参数传递使递归调用成为尾位置调用理论上可被引擎优化为循环执行不过 V8 等主流引擎对尾调用优化TCO的支持有限实际效果需按运行环境验证const factorial (n, acc 1) { if (n 0) throw new TypeError(Negative numbers are not allowed!); return n 1 ? acc : factorial(n - 1, acc * n); };关联阅读同一集合中的递归姊妹篇在 30-seconds-of-code 的 js/recursion 集合 中阶乘与以下片段共享同一递归主题适合串联学习递归入门基准情形、调用栈与栈溢出的概念基础斐波那契数列递归 vs 迭代的另一经典对比最大公约数与最小公倍数欧几里得算法gcd(a, b) gcd(b, a % b)展示了递归在数论计算中的应用其多参数版本还用reduce串联递归函数可反观本片段不推荐 reduce的取舍Big-O Cheat Sheet为复杂度分析提供完整参照表。小结阶乘计算虽小却浓缩了算法设计中两个根本性决策用循环还是递归、如何定义与保护边界。迭代版以 O(1) 空间、O(n) 时间胜在工程效率递归版以与数学定义同构的表达胜在代码优雅与可读性适合作为理解递归的入门台阶并在掌握后进一步用迭代、记忆化或尾递归等手段收敛其开销。需要动手验证时可直接在浏览器控制台或 Node.js 中运行本文代码将factorial(6)替换为你关心的输入值观察两种实现的输出与调用栈行为差异。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐CyberStrikeAI 实战指南一句话启动 AI 安全测试平台100 工具自动编排CyberStrikeAI 实战指南一句话启动 AI 安全测试平台100 工具自动编排 CyberStrikeAI 是一个用 Go 写的 AI 原生安全测教程文档30 seconds of code用递归生成 JavaScript 数组与字符串全排列的完整指南30 seconds of code用递归生成 JavaScript 数组与字符串全排列的完整指南 生成一个数组所有元素或字符串所有字符的全排列是经典算法问教程文档30-seconds-of-code 实战用一行 JavaScript 代码计算任意月份的天数30 seconds of code 实战用一行 JavaScript 代码计算任意月份的天数 导读 在 JavaScript 中处理日期向来不算直观但计教程文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表