当前位置: 首页 > news >正文

LISP递归

在 LISP(以及 Scheme 等方言)中,递归(Recursion)拥有至高无上的地位。

因为经典 LISP 提倡纯粹的函数式编程风格,它没有专门的循环结构(如forwhile。在 LISP 里,所有需要重复执行的逻辑,全部都要通过函数自己调用自己——也就是递归来实现。

理解 LISP 递归,核心在于掌握它的两段式思维和现代 LISP 必须具备的尾递归优化


1. 递归的黄金法则:两段式结构

写 LISP 递归时,脑子里必须时刻清醒地把代码拆成两部分:

  1. 基准条件(Base Case / Terminal Case):什么时候停下来?(通常是遇到空列表nil或数字到了0)。
  2. 递归步骤(Recursive Step):怎么把大问题拆成更小的问题?(通常是处理当前元素,然后把剩下的部分丢给下一个递归)。

经典案例 1:计算阶乘(Factorial)

n!=n×(n−1)×(n−2)×⋯×1n! = n \times (n-1) \times (n-2) \times \dots \times 1n!=n×(n1)×(n2)××1

用 Common LISP 编写如下:

(defunfactorial(n)(if(<=n1)1; 基准条件:n <= 1 时返回 1(*n(factorial(-n1))))); 递归步骤:n * (n-1) 的阶乘

经典案例 2:求列表长度(My-Length)

处理列表(List)是 LISP 的拿手好戏。LISP 内部的数据结构是链表,天然适合用car(获取第一个元素)和cdr(获取剩下所有元素组成的列表)来进行递归。

(defunmy-length(lst)(if(nulllst)0; 基准条件:空列表长度为 0(+1(my-length(cdrlst))))); 递归步骤:1 + 剩下列表的长度

2. 深度剖析:普通递归 vs 尾递归

上面写的两个标准递归虽然直观,但在处理大规模数据时有一个致命弱点:爆栈(Stack Overflow)

普通递归的隐患

看回factorial的这行代码:(* n (factorial (- n 1)))
当程序调用(factorial (- n 1))时,它不能释放当前函数的执行栈,因为云端还有个乘法(* n ...)在等着这个调用结果。

如果要计算(factorial 10000),计算机会在内存里堆叠 10000 个等待返回的函数栈帧(Stack Frame),内存瞬间就被吃光了。

救星:尾递归(Tail Recursion)

如果一个函数在递归调用自己之后,不做任何其他的运算(比如乘法、加法),直接把这个调用结果作为自己的结果返回,这就叫尾递归。

由于后面没事干了,编译器或解释器可以非常聪明地复用当前的栈帧,直接把递归优化成和while循环一模一样的底层机器码,这就叫尾递归优化(Tail Call Optimization, TCO)

怎么把普通递归改写为尾递归?

秘诀在于:引入一个“累加器”(Accumulator),让它在传递参数的过程中顺便把中间结果算好。

我们来改写阶乘:

(defuntail-factorial(n&optional(acc1))(if(<=n1)acc; 最终结果已经存在 acc 里了,直接返回(tail-factorial(-n1)(*n acc)))); 尾递归:调用完自己就结束了,没有任何后续运算

执行轨迹对比(以 3 的阶乘为例):

  • 普通递归:(factorial 3)→\rightarrow(* 3 (factorial 2))→\rightarrow(* 3 (* 2 (factorial 1)))→\rightarrow逐层返回算出来。
  • 尾递归:(tail-factorial 3 1)→\rightarrow(tail-factorial 2 3)→\rightarrow(tail-factorial 1 6)→\rightarrow撞到基准条件,直接把6扔出来,期间只占用一个栈的空间。

3. LISP 递归的经典范式:CAR/CDR 递归

在 LISP 中,最常用的递归模式是同时对car(元素本身)和cdr(剩余列表)进行深度搜索。这常用于处理嵌套列表(树状结构)。

例如,写一个函数count-atoms,统计一个嵌套列表里一共有多少个非空原子元素(不管它嵌套得有多深,比如'(a (b c) d)里有 4 个原子):

(defuncount-atoms(lst)(cond((nulllst)0); 情况 1:空列表,返回 0((atomlst)1); 情况 2:本身是个原子,返回 1(t(+(count-atoms(carlst)); 情况 3:是列表,递归左边(car)(count-atoms(cdrlst)))))); 加上递归右边(cdr)

这个小函数完美展现了 LISP 递归的优雅:通过极其精简的代码,自然地遍历了一棵复杂的非线性树结构。

http://www.rkmt.cn/news/1477784.html

相关文章:

  • 高能中微子天文学:LRDs的发现与物理机制
  • 自主AI代理在数学证明中的边界与实践:从千禧年难题到形式化验证
  • DNN-research
  • 大模型长文本推理基座:从 FlashAttention 硬件加速机制到 vLLM 核心 PagedAttention 显存物理布局深度剖析
  • STS(Spring Tool Suite)从安装到‘开箱即用’:一份给Java新手的保姆级环境配置清单
  • 网易云音乐下载器实战指南:构建完整ID3标签的个人音乐库
  • 不只是编译:深入解读EDK2构建系统变迁,从exe到Python版build工具的背后
  • STM32F103ZET6标准库CAN通信工程包(KEIL可直接编译运行)
  • 2026年Q2机械化垃圾分选系统品牌排行实测盘点:垃圾综合处理、垃圾自动分拣系统、垃圾风选机、填埋场陈腐垃圾分选设备选择指南 - 优质品牌商家
  • 2026年Q2青海包车旅游服务机构排行实测盘点:青甘大环线最佳季节、青甘大环线纯玩旅游、正规青海旅行社、青海包车旅游选择指南 - 优质品牌商家
  • 多维聚合变形:高维数据折叠、拉伸与投影的底层原理
  • 中文新闻文本四模型分类实战代码包:CNN/RNN/GCN/BERT开箱即用
  • 市政仿冒邮件钓鱼攻击特征、检测技术与分层防控实证研究
  • 机器学习在ADHD尿液代谢标志物发现中的应用
  • 立创EDA宝藏库怎么用到AD里?手把手教你创建可复用的集成库文件
  • 2026年垃圾筛分设备权威评测:弹跳筛/智能分选机/机械分选/液压打包机/滚筒筛/生活垃圾资源化利用成套装备/碟盘筛/选择指南 - 优质品牌商家
  • 青海私人定制旅游服务评测:青甘大环线旅游攻略、青甘大环线旅游路线、青甘大环线旅行社、青甘大环线最佳季节、青甘大环线纯玩旅游选择指南 - 优质品牌商家
  • 手把手教你用Python计算并可视化TCP流的Jain公平指数(附数据集与代码)
  • Python中len()函数的底层原理与工程实践指南
  • 别再手动敲代码了!用STM32CubeMX图形化配置FreeRTOS任务与队列(附完整实战代码)
  • Python中len()的真相:不是求长度,而是理解数据结构本质
  • 基于 Harmony 6.0 应用的睡眠质量分析应用首页实现
  • 嵌入式开发中的SpecMap代码映射技术解析
  • 大模型‘中部丢失’现象:Transformer长文本注意力塌陷原理与实战缓解
  • 别再折腾WiFi切换了!让Padavan/OpenWrt路由的打印机和SMB服务对上级网络永久可见
  • AI 赋能下中间人攻击机理与分层防御技术研究
  • Llama 3.1 8B微调实战:低成本实现可靠Function Calling
  • C++嵌入Python解释器实战:零拷贝、异常互通与一键安装
  • 终极指南:如何用NVIDIA Profile Inspector免费解锁显卡隐藏性能
  • 2026产品宣传动画服务商评测:香港安全警示动画、上海事故还原动画、上海工业3D动画、事故还原动画、北京3D动画选择指南 - 优质品牌商家