递归的实际示例[关闭]


95

什么是现实世界的问题,其中一个递归的方法是除了深度优先搜索(DFS)的自然的解决方案?

(我不考虑河内塔楼斐波那契数或现实世界中的阶乘问题。在我看来,这些问题有些人为设置。)


2
感谢所有建议,但每个人都建议遍历树/网络。这些本质上都是深度优先搜索(我猜是BFS)的所有示例。我在寻找其他动机良好的算法/问题。
redfood

10
我喜欢这个问题!“告诉我技术X的所有使用,但技术X的主要实际使用
贾斯汀标准

1
我一直在使用递归,但通常用于数学和图形事物。我正在尝试寻找对非程序员有意义的递归示例。
redfood

6
选择自己的冒险小说!我想阅读整个内容,而递归是最好的方法。
安德烈斯(Andres)2010年

在现实世界中没有递归。递归是数学上的抽象。您可以使用递归来建模很多东西。从这个意义上说,斐波那契绝对是真实世界,因为可以用这种方式对很多真实世界的问题进行建模。如果您认为斐波那契不是真实世界,那么我会声称所有其他示例也是抽象的,而不是真实世界的示例。
Zane

Answers:


41

这里有很多数学示例,但是您需要一个真实的示例,因此,经过一番思考,这可能是我可以提供的最好的示例:

您发现感染了致命性给定的球菌感染并迅速修复的人(A型),只有五分之一的人(被称为B型)被永久性感染,但没有症状,只是充当传播者。

当类型B感染大量类型A时,这会造成非常令人讨厌的破坏波。

您的任务是追踪所有B型B并免疫以阻止疾病的骨干。不幸的是,您无法对所有人实行全国范围的治疗,因为A型患者对B型治疗也具有致命的过敏性。

您可以通过社交发现的方式进行操作,给定一个受感染的人(A型),选择上周的所有联系人,并在堆上标记每个联系人。当您测试某个人被感染时,请将其添加到“跟进”队列中。当一个人是B型时,请将其添加到头部的“跟进”中(因为您想快速停止)。

在处理了给定的人之后,从队列的前面选择该人并根据需要进行免疫接种。获取以前未访问过的所有联系人,然后进行测试以查看是否被感染。

重复此操作,直到被感染人员的队列变为0,然后等待下一次爆发。

(好吧,这有点迭代,但是它是解决递归问题的一种迭代方式,在这种情况下,尝试遍历人口基数以尝试发现问题的可能路径,此外,迭代解决方案通常更快,更有效,而且我强迫性地在所有地方都取消了递归,这本能地.....该死!


2
谢谢-这仍然是图形遍历,但动机很好,对非程序员来说很有意义。
redfood

我相信找到患者0将是一个更好的例子。确定所有可能引起感染的相互作用。对所有在互动时具有传染性的参与者重复上述操作,直到没有传染性被发现为止
William FitzPatrick

3
这个现实世界的例子现在感觉是如此熟悉:(
haroldolivieri

109

递归的真实示例

向日葵


12
由Matrix架构师递归编码:)
Marcel

3
递归如何?当然,很漂亮。但是递归吗?分形的卷心菜本来可以很好地工作,但是我看不到这朵花的自相似性。
克莱门特

1
好吧,这在脸颊上有些许变色,但这是花序轴的一个例子,可以用斐波那契数列(通常通过递归实现)来描述。
Hans Sjunnesson

1
“通常通过递归实现”并不一定意味着花就这样做了。也许是的。我没有足够的分子生物学家知道的,但没有约的解释如何这样做,我不认为这是特别有帮助。投票失败。如果您想添加说明(无论它在生物学上是否准确,它可能会提供一些见识)作为陪伴,我将很高兴重新考虑投票。
lindes

65

文件系统中涉及目录结构的所有内容如何。递归查找文件,删除文件,创建目录等。

这是一个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());
            }
        }       
    }

}

2
文件系统提供了动力(很好,谢谢),但这是DFS的特定示例。
redfood

4
我没有缩写词“ DFS”,因为我坐在教室里已经有一段时间了。
马特·迪拉德

5
深度优先搜索:dfs(node){for node中的每个子节点{visit(child); }
Haoest

对于简单的代码示例,例如见stackoverflow.com/questions/126756/...
Jonik

这段代码有错误吗?不应将getPrivateDirectoryContent()替换为getDirectoryContent()吗?
Shn_Android_Dev


16

马特·迪拉德(Matt Dillard)的榜样很好。更一般地,树的任何行走通常都可以非常容易地通过递归来处理。例如,编译解析树,遍历XML或HTML等。


我发现这个“编译解析树”是一个明智的答案,它涉及树,但仍然不是问问者想要的搜索问题。可以将其概括为某种语言的汇编或解释的一般概念。也可以是“解释”(理解,聆听)自然语言,例如英语。
imz-伊万·扎哈拉里谢夫(Ivan Zakharyaschev)2011年


13

只要可以通过将问题划分为多个子问题来解决问题,则递归是适当的,这些子问题可以使用相同的算法来解决。树和排序列表上的算法很自然。可以使用二进制空间分区(BSP)树,胖细分或其他将世界划分为多个子部分的方式来递归解决计算几何(和3D游戏)中的许多问题。

当您尝试保证算法的正确性时,递归也是合适的。给定一个接受不可变输入并返回结果的函数,该结果是对输入的递归调用和非递归调用的组合,通常可以使用数学归纳法轻松地证明该函数正确(或不正确)。用迭代函数或可能会变异的输入来完成此操作通常很棘手。当处理财务计算和其他应用程序中,正确性非常重要时,这很有用。


11

当然,那里的许多编译器都大量使用递归。计算机语言本身具有递归性(例如,您可以将“ if”语句嵌入其他“ if”语句中,等等)。


嵌入if语句不是递归的。
John Meagher's

但是解析它们需要递归,John。
Apocalisp

2
John,您可以嵌套if语句这一事实意味着语言定义(可能还有语言解析器)是递归的。
德里克公园

递归下降是手工编写编译器的最简单方法之一。不像使用yacc这样的工具容易,但是更容易理解它的工作方式。可以解释整个表驱动状态机,但通常最终会变成黑匣子。

Cody Brocious提到“编译分析树”的答案也指向这一领域:语言分析/解释/编译。
imz-伊万·扎哈拉里舍夫(Ivan Zakharyaschev)2011年

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);
    }
}

8

著名的评估/申请周期从 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))))

这是评估序列的定义:

(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


7

递归用于BSP树之类的事物,用于游戏开发(以及其他类似区域)中的冲突检测。


7

人们通常使用递归方法对文档堆栈进行排序。例如,假设您正在排序100个带有名称的文档。首先按首字母将文件放入一堆,然后对每堆进行分类。

在字典中查找单词通常是通过类似于二进制搜索的技术来执行的,该技术是递归的。

在组织中,老板经常向部门负责人发出命令,而部门负责人又向经理发出命令,依此类推。


5

我最近得到的现实世界要求:

要求A:在完全理解要求A之后,实施此功能。


4

解析器和编译器可以递归下降方法编写。这不是最好的方法,因为lex / yacc之类的工具生成更快,更高效的解析器,但是从概念上讲简单易行,因此它们仍然很常见。


4

递归应用于问题(情况),您可以将其分解(减少)成较小的部分,每个部分看起来都类似于原始问题。

包含与自己相似的较小部分的事物的很好的例子是:

  • 树状结构(树枝就像一棵树)
  • 列表(列表的一部分仍然是列表)
  • 容器(俄罗斯娃娃)
  • 序列(序列的一部分看起来像下一个)
  • 对象组(子组仍然是对象组)

递归是一种将问题不断分解为越来越小的技术,直到其中一个变得足够小以至于成为小菜一碟。当然,将它们分解后,您必须按照正确的顺序将结果“缝合”在一起,以形成原始问题的整体解决方案。

一些递归排序算法,树遍历算法,地图/归约算法,分治法都是该技术的示例。

在计算机编程中,大多数基于堆栈的调用返回类型语言已经具有内置的递归功能:即

  • 将问题分解为更小的部分==>在原始数据的较小子集上调用自身),
  • 跟踪碎片的分割方式==>调用堆栈,
  • 将结果缝回==>基于堆栈的返回


4

函数式编程语言中可以找到一些很好的递归示例。使用函数式编程语言(ErlangHaskellML / 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的值(因此,我们的递归)。

例如,我可能有一个URL列表,我认为将每个URL链接到的所有URL分开,然后减少到/来自所有URL的链接总数,以为页面生成“值”(这种方法是Google与PageRank一起使用,您可以在原始MapReduce论文中找到定义)。您也可以执行此操作以在文档中生成字数统计。还有很多很多其他的东西。

您可以将此功能模式扩展到任何类型的MapReduce代码,在其中可以获取内容列表,进行转换并返回其他内容(无论是其他列表还是列表上的某些zip命令)。


3

XML,或遍历任何树。虽然,老实说,我几乎从未在工作中使用递归。


甚至没有尾巴递归?
Apocalisp

我在职业生涯中曾经使用过递归,当框架发生变化时,我摆脱了递归。我们做的80%是CRUD。
Charles Graham

1
首先提到“ XML”是很奇怪的。这不是自然的事情,也不是您要教的平凡人在日常生活中必须面对的事情。但是这个想法当然是很明智的。
imz-伊万·扎哈拉里谢夫(Ivan Zakharyaschev)2011年

3

反馈在分层组织中循环。

高层老板告诉高层管理人员收集公司所有人的反馈。

每位高管都收集他/她的直接报告,并告诉他们从他们的直接报告中收集反馈。

然后就行了。

没有直接报告的人-树中的叶子节点-提供反馈。

反馈将回溯到树上,每个经理都添加自己的反馈。

最终,所有反馈都将其反馈给高层领导。

这是自然的解决方案,因为递归方法允许在每个级别进行过滤-整理重复项并消除令人反感的反馈。最高负责人可以发送全球电子邮件,并让每个员工直接将反馈报告反馈给他/她,但是存在“您无法处理真相”和“您被解雇”的问题,因此递归在此处效果最佳。


2

假设您正在为网站构建CMS,其中您的页面是树状结构,并且说根是主页。

假设您的{user | client | customer | boss}要求您在每个页面上放置一个面包屑痕迹,以显示您在树中的位置。

对于任何给定的页面n,您可能需要递归到n的父级及其父级,依此类推,以递归的方式建立一个节点列表,以返回到页面树的根目录。

当然,在该示例中,您在每个页面上多次击中数据库,因此您可能需要使用一些SQL别名,在其中将page-table查找为a,然后将page-table查找为b,然后将a.id与b.parent,以便您使数据库执行递归联接。已经有一段时间了,所以我的语法可能没有帮助。

再一次,您可能只想计算一次并将其与页面记录一起存储,仅在移动页面时才对其进行更新。那可能会更有效率。

无论如何,那是我的$ .02


2

您有一个N层深的组织树。已检查了几个节点,并且您想扩展到仅已检查的那些节点。

这是我实际编码的东西。递归很容易。


2

在我的工作中,我们有一个具有通用数据结构的系统,该系统可以描述为树。这意味着递归是处理数据的一种非常有效的技术。

不递归地解决它将需要很多不必要的代码。递归的问题是要跟踪发生的事情并不容易。遵循执行流程时,您确实必须专心。但是当它起作用时,代码是优雅而有效的。



2
  • 解析XML文件。
  • 在多维空间中的高效搜索。例如 2D中的四叉树,3D中的八叉树,kd树等。
  • 层次聚类。
  • 想一想,遍历任何层次结构自然会使其递归。
  • C ++中的模板元编程,其中没有循环,而递归是唯一的方法。

“ XML”对于这个答案的想法不是必不可少的(特别提到XML可能会使您正在教的人感到恶心/无聊)。递归解析问题仅以任何一种典型的语言(计算机语言或自然语言)为例。
imz-伊万·扎哈拉里谢夫(Ivan Zakharyaschev)2011年

张贴者要求“递归方法是自然解决方案的实际问题”。解析xml文件无疑是一个现实问题,它自然很适合进行递归操作。您似乎对XML有一些奇怪的厌恶,但这并不能改变它被广泛使用的事实。
迪马


2

我知道的最好的例子是quicksort,它与递归相比要简单得多。看一眼:

shop.oreilly.com/product/9780596510046.do

www.amazon.com/zh-CN/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047

(单击第3章“我写过的最漂亮的代码”下的第一个字幕)。


1
而且MergeSort的递归也更简单。
马修·申克尔

1
链接断开。您可以添加书名吗?
彼得·莫滕森

@PeterMortensen,这本书是格雷格·威尔逊(Greg Wilson)和安迪·奥兰(Andy Oram)编写的《美丽代码》。我更新了链接,但似乎O'Reilly不允许再窥视内部。但是您可以看看亚马逊。
Fabio Ceconello 2014年

1

电话和电缆公司维护其布线拓扑的模型,该模型实际上是一个大型网络或图形。当您要查找所有父元素或所有子元素时,递归是遍历此模型的一种方法。

由于从处理和内存的角度来看递归是昂贵的,因此通常仅在更改拓扑并将结果以修改的预排序列表格式存储时才执行此步骤。


1

归纳推理是概念形成的过程,本质上是递归的。您的大脑在现实世界中始终无休止地工作。


1

同上关于编译器的评论。自然地,抽象语法树节点适合于递归。使用递归还可以更轻松地处理所有递归数据结构(链接列表,树,图等)。我确实认为,由于现实问题的种类,我们大多数人在放学后就不会经常使用递归,但是最好将其作为一种选择。


1

自然数的乘法是递归的真实示例:

To multiply x by y
  if x is 0
    the answer is 0
  if x is 1
    the answer is y
  otherwise
    multiply x - 1 by y, and add x
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.