递归的真实示例[关闭]
Posted
技术标签:
【中文标题】递归的真实示例[关闭]【英文标题】:Real-world examples of recursion [closed] 【发布时间】:2010-09-11 11:23:02 【问题描述】:除了深度优先搜索 (DFS) 之外,递归方法是自然解决方案的现实世界问题是什么?
(我不考虑Tower of Hanoi、Fibonacci number 或阶乘现实问题。在我看来,它们有点做作。)
【问题讨论】:
感谢所有建议,但每个人都建议树/网络遍历。这些基本上都是深度优先搜索(我猜是 BFS)的例子。我一直在寻找其他动机良好的算法/问题。 我喜欢这个问题! “告诉我技术 X 的所有用途,除了技术 X 的主要实际用途” 我一直使用递归,但通常用于数学和图形方面的事情。我正在尝试寻找对非程序员有意义的递归示例。 选择你自己的冒险小说!我想通读全文,递归是最好的方法。 现实世界中没有递归。递归是一种数学抽象。您可以使用递归对很多事物进行建模。从这个意义上说,斐波那契绝对是真实世界的,因为有很多真实世界的问题可以用这种方式建模。如果您认为斐波那契不是真实世界的,那么我会声称所有其他示例也是抽象的,而不是真实世界的示例。 【参考方案1】:递归的真实示例
【讨论】:
Matrix 架构师使用递归编码 :) 这是如何递归的?当然,它很漂亮。但是递归?分形卷心菜效果很好,但我看不出这朵花有自相似性。 好吧,这有点扯,但它是叶序的一个例子,可以用斐波那契数列来描述,通常通过递归实现。 “通常通过递归实现”并不一定意味着花这样做。也许确实如此;作为分子生物学家,我还不够了解,但如果没有解释它如何,我认为这不是特别有用。投反对票。如果你想添加描述(无论它是否在生物学上准确,它可能会提供洞察力)来伴随它,我很乐意重新考虑投票。 喜欢这个例子!!!【参考方案2】:文件系统中涉及目录结构的任何事情怎么样。递归查找文件、删除文件、创建目录等
这是一个递归打印目录及其子目录内容的 Java 实现。
import java.io.File;
public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser
private static StringBuilder indentation = new StringBuilder();
public static void main (String args [] )
// Here you pass the path to the directory to be scanned
getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
private static void getDirectoryContent(String filePath)
File currentDirOrFile = new File(filePath);
if ( !currentDirOrFile.exists() )
return;
else if ( currentDirOrFile.isFile() )
System.out.println(indentation + currentDirOrFile.getName());
return;
else
System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
indentation.append(" ");
for ( String currentFileOrDirName : currentDirOrFile.list())
getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
if (indentation.length() - 3 > 3 )
indentation.delete(indentation.length() - 3, indentation.length());
【讨论】:
文件系统提供动力(这很好,谢谢),但这是 DFS 的一个具体示例。 我没有得到首字母缩写词“DFS”——我已经有一段时间没有坐在教室里了。 深度优先搜索:dfs( node ) foreach child in node visit( child ); 对于简单的代码示例,请参见例如***.com/questions/126756/… 这段代码有错误吗?不应该用 getDirectoryContent() 替换 getPrivateDirectoryContent() 吗?【参考方案3】:这里有很多数学例子,但你想要一个现实世界的例子,所以稍微思考一下,这可能是我能提供的最好的例子:
您会发现一个人感染了某种非致命性的传染性感染,并且很快就会自我修复(A 型),除了五分之一的人(我们称之为 B 型)永久感染该病毒并且没有表现出任何症状,只是充当传播者。
当 B 型感染大量 A 型时,这会造成相当烦人的破坏。
您的任务是追踪所有 B 型并对其进行免疫以阻止疾病的主干。不幸的是,你不能在全国范围内对所有人进行治疗,因为 A 型的人对 B 型的治疗也有致命的过敏。
你会这样做的方式是社交发现,给定一个感染者(A 型),选择他们上周的所有联系人,将每个联系人标记在一个堆上。当您测试一个人被感染时,将他们添加到“跟进”队列中。当一个人是B型时,将他们添加到头部的“跟进”中(因为您想快速停止)。
处理给定人员后,从队列的最前面选择人员并在需要时进行免疫接种。获取他们之前未访问过的所有联系人,然后测试他们是否被感染。
重复直到感染者队列变为0,然后等待再次爆发..
( 好吧,这有点迭代,但它是解决递归问题的一种迭代方式,在这种情况下,广度优先遍历人口基数试图发现问题的可能路径,此外,迭代解决方案通常更快并且更有效,而且我强迫性地在所有地方删除递归,以至于它变得本能......该死!)
【讨论】:
谢谢 - 这仍然是图遍历,但它的动机很好,对非程序员来说很有意义。 我相信找到病人 0 会是一个更好的例子。确定所有可能导致感染的相互作用。对在交互时具有传染性的所有相关人员重复此操作,直到没有发现传染性为止 这个真实的例子现在感觉很熟悉:( 2020 年读起来太可怕了。它看起来像是预测而不是答案。肯特描述它的方式与covid19完全匹配。我们可以说中国从这里得到了这个想法吗:P 2020 年有意义!!!!【参考方案4】:Quicksort、merge sort 和大多数其他 N-log N 排序。
【讨论】:
【参考方案5】:马特迪拉德的例子很好。更一般地说,树的任何行走通常都可以很容易地通过递归来处理。例如,编译解析树、遍历 XML 或 html 等。
【讨论】:
我发现这个“编译解析树”是一个明智的答案,它涉及树,但仍然不是提问者想要的搜索问题。它可以推广到某种语言的编译或解释的一般概念。它也可以是“解释”(理解、聆听)一种自然语言,例如英语。【参考方案6】:递归经常用于Backtracking algorithm 的实现。对于这个的“真实世界”应用程序,Sudoku solver 怎么样?
【讨论】:
一个状态数组和一个低可以加快这个速度。【参考方案7】:只要可以通过将问题划分为子问题来解决问题,就可以使用递归,这些子问题可以使用相同的算法来解决这些问题。树和排序列表上的算法是天作之合。计算几何(和 3D 游戏)中的许多问题都可以使用binary space partitioning (BSP) 树、fat subdivisions 或其他将世界划分为子部分的方式递归解决。
当您试图保证算法的正确性时,递归也是合适的。给定一个函数,它接受不可变的输入并返回一个结果,该结果是对输入的递归和非递归调用的组合,使用数学归纳法通常很容易证明该函数是正确的(或不正确的)。使用迭代函数或可能发生变异的输入通常很难做到这一点。这在处理财务计算和其他对正确性非常重要的应用程序时很有用。
【讨论】:
【参考方案8】:肯定有很多编译器大量使用递归。计算机语言本身就是递归的(即,您可以将“if”语句嵌入到其他“if”语句中,等等)。
【讨论】:
嵌入的 if 语句不是递归的。 但是解析它们需要递归,约翰。 John,您可以嵌套 if 语句这一事实意味着语言定义(可能还有语言解析器)是递归的。 递归下降是手动编写编译器的最简单方法之一。不像使用 yacc 这样的工具那么容易,但更容易理解它是如何工作的。整个表驱动的状态机可以解释,但通常最终是黑盒子。 Cody Brocious 的回答提到“编译解析树”也指向了这个领域:语言分析/解释/编译。【参考方案9】:对容器控件中的所有子控件禁用/设置只读。我需要这样做,因为一些子控件本身就是容器。
public static void SetReadOnly(Control ctrl, bool readOnly)
//set the control read only
SetControlReadOnly(ctrl, readOnly);
if (ctrl.Controls != null && ctrl.Controls.Count > 0)
//recursively loop through all child controls
foreach (Control c in ctrl.Controls)
SetReadOnly(c, readOnly);
【讨论】:
【参考方案10】:人们经常使用递归方法对成堆的文档进行排序。例如,假设您正在对 100 个带有名称的文档进行排序。首先将文档按第一个字母成堆,然后对每一堆进行排序。
在字典中查找单词通常是通过类似于二进制搜索的技术来执行的,该技术是递归的。
在组织中,老板经常向部门负责人下达命令,部门负责人又向经理下达命令,等等。
【讨论】:
【参考方案11】:来自SICP 的著名评估/应用周期
(来源:mit.edu)
这里是eval的定义:
(define (eval exp env)
(cond ((self-evaluating? exp) exp)
((variable? exp) (lookup-variable-value exp env))
((quoted? exp) (text-of-quotation exp))
((assignment? exp) (eval-assignment exp env))
((definition? exp) (eval-definition exp env))
((if? exp) (eval-if exp env))
((lambda? exp)
(make-procedure (lambda-parameters exp)
(lambda-body exp)
env))
((begin? exp)
(eval-sequence (begin-actions exp) env))
((cond? exp) (eval (cond->if exp) env))
((application? exp)
(apply (eval (operator exp) env)
(list-of-values (operands exp) env)))
(else
(error "Unknown expression type - EVAL" exp))))
这里是apply的定义:
(define (apply procedure arguments)
(cond ((primitive-procedure? procedure)
(apply-primitive-procedure procedure arguments))
((compound-procedure? procedure)
(eval-sequence
(procedure-body procedure)
(extend-environment
(procedure-parameters procedure)
arguments
(procedure-environment procedure))))
(else
(error
"Unknown procedure type - APPLY" procedure))))
这里是eval-sequence的定义:
(define (eval-sequence exps env)
(cond ((last-exp? exps) (eval (first-exp exps) env))
(else (eval (first-exp exps) env)
(eval-sequence (rest-exps exps) env))))
eval
-> apply
-> eval-sequence
-> eval
【讨论】:
【参考方案12】:递归用于游戏开发(和其他类似领域)中的碰撞检测,例如 BSP 树。
【讨论】:
【参考方案13】:我最近得到的真实世界需求:
需求 A:在彻底理解需求 A 后实现此功能。
【讨论】:
【参考方案14】:解析器和编译器可以用递归下降法编写。这不是最好的方法,因为 lex/yacc 之类的工具可以生成更快、更高效的解析器,但在概念上简单且易于实现,因此它们仍然很常见。
【讨论】:
【参考方案15】:递归应用于问题(情况),您可以将其分解(减少)成更小的部分,每个部分看起来都与原始问题相似。
包含与其自身相似的较小部分的事物的好例子是:
树形结构(一个分支就像一棵树) 列表(列表的一部分仍然是列表) 集装箱(俄罗斯娃娃) 序列(序列的一部分看起来像下一个) 对象组(子组仍然是一组对象)递归是一种不断将问题分解成越来越小的部分的技术,直到其中一个部分变得小到可以小菜一碟。当然,在将它们分解后,您必须将结果以正确的顺序“缝合”在一起,以形成原始问题的完整解决方案。
一些递归排序算法、tree-walking 算法、map/reduce 算法、分治法都是这种技术的例子。
在计算机编程中,大多数基于堆栈的调用返回类型语言已经具有内置的递归功能:即
将问题分解成更小的部分 ==> 在原始数据的较小子集上调用自身), 跟踪片段的划分方式 ==> 调用堆栈, 将结果缝合回去 ==> 基于堆栈的返回【讨论】:
【参考方案16】:分层组织中的反馈循环。
高层老板告诉高层管理人员收集公司每个人的反馈。
每位高管都会收集他/她的直接下属,并告诉他们从直接下属那里收集反馈。
接下来就是。
没有直接下属的人——树中的叶子节点——给出他们的反馈。
反馈会随着每个经理添加他/她自己的反馈返回到树上。
最终所有的反馈都会反馈给最高老板。
这是自然的解决方案,因为递归方法允许在每个级别进行过滤 - 整理重复项和删除令人反感的反馈。最高老板可以发送全球电子邮件,让每个员工直接向他/她报告反馈,但存在“你无法处理真相”和“你被解雇”的问题,所以递归在这里效果最好。
【讨论】:
【参考方案17】:我有一个系统在一些地方使用纯tail recursion 来模拟状态机。
【讨论】:
【参考方案18】:在functional programming 语言中可以找到一些很好的递归示例。在函数式编程语言(Erlang、Haskell、ML/OCaml/F# 等)中,任何列表处理都使用递归是很常见的。
在使用典型的命令式 OOP 风格语言处理列表时,将列表实现为链表([item1 -> item2 -> item3 -> item4])是很常见的。但是,在某些函数式编程语言中,您会发现列表本身是递归实现的,其中列表的“头”指向列表中的第一项,“尾”指向包含其余项目的列表( [item1 -> [item2 -> [item3 -> [item4 -> []]]]])。在我看来,这很有创意。
当与模式匹配结合使用时,这种对列表的处理非常强大。假设我想总结一个数字列表:
let rec Sum numbers =
match numbers with
| [] -> 0
| head::tail -> head + Sum tail
这实质上是说“如果我们被一个空列表调用,则返回 0”(允许我们中断递归),否则返回 head 的值 + 使用剩余项调用的 Sum 的值(因此,我们的递归) .
例如,我可能有一个URLs 列表,我认为拆分每个 URL 链接到的所有 URL,然后我减少所有 URL 的链接总数以生成页面的“值” (Google 采用PageRank 的一种方法,您可以在原始MapReduce 论文中找到它的定义)。您也可以这样做以在文档中生成字数统计。还有很多很多其他的东西。
您可以将此功能模式扩展到任何类型的MapReduce 代码,您可以在其中获取某些内容的列表、对其进行转换并返回其他内容(无论是另一个列表,还是列表中的某个 zip 命令)。
【讨论】:
【参考方案19】:XML,或遍历任何树。虽然,老实说,我几乎从不在我的工作中使用递归。
【讨论】:
连尾递归都没有? 我在职业生涯中使用过一次递归,当框架发生变化时,我就摆脱了它。我们所做的 80% 都是 CRUD。 首先提到“XML”是很奇怪的。这不是一件自然的事情,不是你要教的普通人在日常生活中必须处理的事情。但这个想法当然是相当明智的。【参考方案20】:通过递归解决的“现实世界”问题是套娃。你的函数是 OpenDoll()。
给定一堆玩偶,你会递归打开玩偶,如果你愿意,调用 OpenDoll(),直到你到达最里面的玩偶。
【讨论】:
我喜欢这个简单的答案。【参考方案21】:假设您正在为一个网站构建一个 CMS,其中您的页面采用树形结构,比如根是主页。
还假设您的 user|client|customer|boss 请求您在每个页面上放置一个面包屑路径以显示您在树中的位置。
对于任何给定的页面 n,您可能希望遍历 n 的父级及其父级,以此类推,以递归方式构建一个节点列表,然后返回到页面树的根节点。
当然,在该示例中,您在每页中多次访问 db,因此您可能希望使用一些 SQL 别名,将页表查找为 a,将页表再次查找为 b,然后加入 a .id 和 b.parent,这样你就可以让数据库进行递归连接。已经有一段时间了,所以我的语法可能没有帮助。
再一次,您可能只想计算一次并将其与页面记录一起存储,仅当您移动页面时才更新它。那可能会更有效率。
无论如何,那是我的 $.02
【讨论】:
【参考方案22】:您有一个深度为 N 层的组织树。有几个节点被检查了,而您想只扩展那些已经检查的节点。
这是我实际编写的代码。 递归很好,很容易。
【讨论】:
【参考方案23】:在我的工作中,我们有一个具有通用数据结构的系统,可以被描述为一棵树。这意味着递归是一种非常有效的数据处理技术。
不使用递归解决它需要大量不必要的代码。递归的问题在于,要跟踪所发生的事情并不容易。在遵循执行流程时,您确实必须集中精力。但是当它运行时,代码是优雅而有效的。
【讨论】:
【参考方案24】:金融/物理计算,例如复合平均数。
【讨论】:
【参考方案25】: 解析XML 文件。 在多维空间中进行高效搜索。例如。 2D 四叉树、3D 八叉树、kd 树等。 层次聚类。 想想看,遍历任何层次结构自然会导致递归。 C++ 中的模板元编程,没有循环,递归是唯一的方法。【讨论】:
"XML" 对于这个答案的想法并不重要(特别提到 XML 可能会让您正在教的人感到恶心/无聊)。任何典型的语言(计算机语言或自然语言)都可以作为递归解析问题的示例。 发帖者询问“递归方法是自然解决方案的现实问题”。解析 xml 文件肯定是一个现实世界的问题,它自然而然地适合递归。您似乎对 XML 有一些奇怪的反感这一事实并没有改变它被广泛使用的事实。【参考方案26】:解析Windows Forms 或WebForms (.NET Windows Forms / ASP.NET) 中的控件树。
【讨论】:
【参考方案27】:我知道的最好的例子是quicksort,它使用递归要简单得多。看看:
shop.oreilly.com/product/9780596510046.do
www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047
(点击第3章下的第一个副标题:“我写过的最漂亮的代码”)。
【讨论】:
而 MergeSort 使用递归也更简单。 链接已损坏。可以加书名吗? @PeterMortensen,这本书是 Greg Wilson 和 Andy Oram 的 Beautiful Code。我更新了链接,但似乎 O'Reilly 不允许再偷看里面了。但是你可以看看亚马逊。【参考方案28】:电话和有线电视公司维护其布线拓扑模型,这实际上是一个大型网络或图形。当您想要查找所有父元素或所有子元素时,递归是遍历此模型的一种方式。
由于从处理和内存的角度来看,递归是昂贵的,因此此步骤通常仅在拓扑发生更改并且结果以修改后的预排序列表格式存储时执行。
【讨论】:
【参考方案29】:归纳推理,即概念形成的过程,本质上是递归的。在现实世界中,你的大脑一直在这样做。
【讨论】:
【参考方案30】:同上关于编译器的评论。抽象语法树节点自然适合递归。所有递归数据结构(链表、树、图等)也更容易使用递归处理。我确实认为,由于现实世界问题的类型,我们中的大多数人一旦离开学校就不会经常使用递归,但最好意识到它是一种选择。
【讨论】:
以上是关于递归的真实示例[关闭]的主要内容,如果未能解决你的问题,请参考以下文章