没有阶乘,斐波那契数等的递归


47

我几乎可以找到有关递归的每篇文章,其中包括阶乘或斐波那契数的示例,它们是:

  1. 数学
  2. 在现实生活中无用

是否有一些有趣的非数学代码示例来教授递归?

我在考虑分而治之算法,但它们通常涉及复杂的数据结构。


26
当您的问题完全成立时,我会毫不犹豫地称斐波那契数在现实生活中毫无用处。同去的阶乘
Zach L

2
Little Schemer是一本关于递归的完整书籍,从未使用过Fact或Fib。junix-linux-config.googlecode.com/files/…–
Eric Wilson

5
@Zach:即使如此,由于指数运行时间的原因,递归是实现斐波那契数的一种可怕方法。
dan04 2011年

2
@ dan04:由于大多数lanaguage中都有堆栈溢出的可能,因此递归是一种几乎可以实现任何事情的可怕方法。
R.,

5
@ dan04,除非您的语言足够聪明,能够像大多数功能语言一样实现尾部调用优化,在这种情况下它仍然可以正常工作
Zachary K

Answers:


107

目录/文件结构是递归用法的最佳示例,因为每个人都可以在开始之前就了解它们,但是任何涉及树状结构的事物都可以使用。

void GetAllFilePaths(Directory dir, List<string> paths)
{
    foreach(File file in dir.Files)
    {
        paths.Add(file.Path);
    }

    foreach(Directory subdir in dir.Directories)
    {
        GetAllFilePaths(subdir, paths)
    }
}

List<string> GetAllFilePaths(Directory dir)
{
    List<string> paths = new List<string>();
    GetAllFilePaths(dir, paths);
    return paths;
}

1
谢谢,我想我将使用文件系统。这是一个具体的东西,可以用于许多实际示例。
突触

9
注意:unix命令通常会取消-r选项(例如cp或rm)。-r代表递归。
deadalnix

7
您在这里一定要格外小心,因为在现实世界中,文件系统实际上是有向图,不一定是树,如果文件系统支持,则硬链接等也可以创建连接和循环
jk。

1
@jk:目录无法进行硬链接,因此对可能会出现在多个位置的某些叶子进行模运算,并假设排除符号链接,则实际文件系统是树。
R..

1
在某些文件系统中,目录还有其他特性,例如NTFS重解析点。我的观点是代码,没有明确意识到这可能对现实世界的文件系统意想不到的结果
JK。

51

寻找涉及树形结构的事物。一棵树相对容易掌握,并且与线性数据结构(例如列表)相比,递归解决方案的优势变得更加明显。

要考虑的事情:

  • 文件系统-基本上是树;一个不错的编程任务是获取某个目录及其所有子目录下的所有.jpg图像
  • 祖先-给定一棵家谱,找到要寻找共同祖先所必须走的世代数;或检查树中的两个人是否属于同一世代;或检查树上的两个人是否可以合法结婚(取决于管辖权:)
  • 类似于HTML的文档-在文档的序列(文本)表示形式和DOM树之间进行转换;在DOM子集上执行操作(甚至可以实现xpath的子集?);...

这些都与实际的实际场景有关,并且都可以用于具有实际意义的应用程序中。


当然,应该指出的是,只要您有自由设计自己的树结构,保持指向父/下一个兄弟/等的指针/引用几乎总是更好。在节点中,这样您就可以遍历树而无需递归。
R..

1
指针与它有什么关系?即使您有指向第一个孩子,下一个兄弟姐妹和父母的指针,也仍然必须以某种方式遍历树,在某些情况下,递归是最可行的方法。
tdammers 2011年

40

https://stackoverflow.com/questions/105838/real-world-examples-of-recursion

  • 模拟传染性感染
  • 生成几何
  • 目录管理
  • 分类

https://stackoverflow.com/questions/2085834/how-did-you-practically-use-recursion

  • 光线追踪
  • 解析源代码(语言语法)

https://stackoverflow.com/questions/4945128/what-is-a-good-example-of-recursion-other-than-generating-a-fibonacci-sequence

  • BST搜索
  • 河内塔
  • 回文搜索

https://stackoverflow.com/questions/126756/examples-of-recursive-functions

  • 给出一个不错的英语故事,该故事通过就寝时间的故事来说明递归。

10
尽管从理论上讲这可以回答问题,但最好在此处包括这些问题和答案的必要部分,并提供链接以供参考。如果问题从SO中删除,您的答案将完全无用。
亚当李尔

@Anna好吧,用户无法删除他们的问题,因此发生的可能性有多大?
vemv 2011年

4
@vemv删除有关主题更改的投票,主持人,规则……这可能发生。无论哪种方式,在这里获得更完整的答案都比立即将访问者发送到四个不同页面要好。
亚当李尔

@Anna:按照这种思路,每个封闭为“精确重复”的问题都应粘贴原始答案。这个问题是(SO上问题的)完全重复,为什么要接受与完全不同的对待有关程序员的问题重复吗?
SF。

1
@SF如果可以将其作为重复项关闭,则可以,但是不支持跨站点重复项。程序员是一个单独的站点,因此理想的答案是将SO用作任何其他参考,而不是完全委托给它。这与说“您的答案在本书中”没有什么不同-从技术上讲是正确的,但是如果不咨询参考文献就不能立即使用。
亚当李尔

22

我想到了一些更实际的问题:

  • 合并排序
  • 二进制搜索
  • 在树上的遍历,插入和删除(主要用于数据库应用程序)
  • 排列发生器
  • 数独解算器(具有回溯功能)
  • 拼写检查(还是回溯)
  • 语法分析(例如,将前缀转换为后缀表示法的程序)

11

QuickSort将是第一个让人想到的。二进制搜索也是一个递归问题。除此之外,还存在一整类问题,当您开始使用递归时,解决方案几乎是免费的。


3
二进制搜索通常被公式化为递归问题,但是以命令式的方式实现它是微不足道的(并且通常是更可取的)。
蓬松的

取决于您使用的是哪种语言,代码可能会也可能不会明确地递归,强制性或递归。但这仍然是一种递归算法,因为您可以将问题分解为越来越小的块,以获取解决方案。
Zachary K

2
@Zachary:可以通过尾部递归实现的算法(如二进制搜索)与需要真正递归的算法(或您自己的状态结构,空间需求同样昂贵)在根本上是不同的空间类。我认为像他们一样一样一起学习对他们没有好处。
R..

8

排序,在Python中递归定义。

def sort( a ):
    if len(a) == 1: return a
    part1= sort( a[:len(a)//2] )
    part2= sort( a[len(a)//2:] )
    return merge( part1, part2 )

合并,以递归方式定义。

def merge( a, b ):
    if len(b) == 0: return a
    if len(a) == 0: return b
    if a[0] < b[0]:
        return [ a[0] ] + merge(a[1:], b)
    else:
        return [ b[0] ] + merge(a, b[1:]) 

线性搜索,以递归方式定义。

def find( element, sequence ):
    if len(sequence) == 0: return False
    if element == sequence[0]: return True
    return find( element, sequence[1:] )

二进制搜索,以递归方式定义。

def binsearch( element, sequence ):
    if len(sequence) == 0: return False
    mid = len(sequence)//2
    if element < mid: 
        return binsearch( element, sequence[:mid] )
    else:
        return binsearch( element, sequence[mid:] )

6

从某种意义上讲,递归就是解决问题的方法,即将问题空间缩小为一个较小的空间,以帮助找到一个简单问题的解决方案,然后通常回头重建原始问题以构成正确的答案。

一些不涉及数学的递归教学示例(至少我在大学时代记得的那些问题):

这些是使用回溯解决问题的示例。

其他问题是人工智能领域的经典问题:使用深度优先搜索,寻路,规划。

所有这些问题都涉及某种“复杂”的数据结构,但是如果您不想用数学(数字)来教它,那么您的选择可能会更加有限。Yoy可能想开始使用基本数据结构(如链表)进行教学。例如,使用列表表示自然数:

0 =空列表1 =具有一个节点的列表。2 =列出2个节点。...

然后根据此数据结构定义两个数字的总和,如下所示:空+ N = N Node(X)+ N = Node(X + N)


5

河内塔是帮助学习递归的好人。

网络上有许多不同语言的解决方案。


3
我认为这实际上是另一个不好的例子。首先,这是不现实的。这不是人们实际遇到的问题。其次,有简单的非递归解决方案。(一个是:为磁盘编号。永远不要将磁盘移动到相同奇偶校验的磁盘上,也不要撤消上一步的操作。如果遵循这两个规则,则将以最佳解决方案解决难题。无需递归。 )
埃里克·利珀特

5

回文检测器:

以字符串开头:“ ABCDEEDCBA”如果开始和结束字符相等,则递归并检查“ BCDEEDCB”,等等。


6
在没有递归的情况下解决这个问题也很简单,恕我直言,在没有递归的情况下可以更好地解决。
Blrfl 2011年

3
同意,但OP特别要求提供教学示例,以尽量减少数据结构的使用。
NWS

5
如果您的学生可以立即想到一种非递归解决方案,那么这不是一个很好的教学示例。当您的示例为“这里有一个循环琐碎的事情。现在,我将向您展示更困难的方法,没有明显的原因”时,为什么有人会注意。
恢复莫妮卡


4

在函数式编程语言中,当没有高阶函数可用时,将使用递归而不是命令式循环,以避免可变状态。

F#是一种不纯净的功能语言,它同时允许两种样式,因此我将在此处进行比较。以下是列表中所有数字的总和。

具有可变变量的命令式循环

let xlist = [1;2;3;4;5;6;7;8;9;10]
let mutable sum = 0
for x in xlist do
    sum <- sum + x

没有可变状态的递归循环

let xlist = [1;2;3;4;5;6;7;8;9;10]
let rec loop sum xlist = 
    match xlist with
    | [] -> sum
    | x::xlist -> loop (sum + x) xlist
let sum = loop 0 xlist

请注意,这种聚合在高阶函数中捕获的List.fold,可以将其写为List.fold (+) 0 xlist,甚至可以更简单地用便利函数List.sumjust 编写List.sum xlist


您所获得的积分将不只是我的+1。没有递归的F#将会非常乏味,对于没有循环构造的Haskell来说,这尤其如此!
schoetbi 2011年

3

我在游戏AI中大量使用了递归。用C ++编写时,我使用了一系列约7个函数,这些函数按顺序相互调用(第一个函数可以绕过所有这些函数,而是调用另外2个函数链)。任一个链中的最终函数再次调用第一个函数,直到我要搜索的剩余深度变为0,在这种情况下,最终函数将调用我的评估函数并返回位置得分。

多种功能使我可以轻松地根据玩家的决定或游戏中的随机事件进行分支。我会尽可能使用传递引用,因为我要传递非常大的数据结构,但是由于游戏的结构方式,很难在搜索中进行“撤消”操作,因此我会在某些函数中使用按值传递,以保持原始数据不变。因此,事实证明切换到基于循环的方法而不是递归方法太困难了。

您可以看到此类程序的非常基本的概述,请参阅https://secure.wikimedia.org/wikipedia/en/wiki/Minimax#Pseudocode


3

商业中一个非常好的现实生活例子就是所谓的“物料清单”。这是代表组成成品的所有组件的数据。例如,让我们使用自行车。自行车具有车把,车轮,车架等。每个组件都可以包含子组件。例如,车轮可以具有辐条,气门杆等。因此,通常以树状结构表示。

现在,您经常需要递归来查询有关BOM的任何汇总信息或更改BOM中的元素。

    class BomPart
    {
        public string PartNumber { get; set; }
        public string Desription { get; set; }
        public int Quantity { get; set; }
        public bool Plastic { get; set; }
        public List<BomPart> Components = new List<BomPart>();
    }

还有一个示例递归调用...

    static int ComponentCount(BomPart part)
    {
        int subCount = 0;
        foreach(BomPart p in part.Components)
            subCount += ComponentCount(p);
        return part.Quantity * Math.Max(1,subCount);

    }

显然,BomPart类将具有更多的字段。您可能需要弄清楚您有多少个塑料组件,制造一个完整零件需要多少人工等。所有这些都回到了递归在树结构上的有用性。


物料清单可以是定向砂,而不是树,例如,不止一个组件可以使用相同规格的螺钉。
伊恩

-1

家庭关系是一个很好的例子,因为每个人都能直观地理解它们:

ancestor(joe, me) = (joe == me) 
                    OR ancestor(joe, me.father) 
                    OR ancestor(joe, me.mother);

该代码用哪种语言编写?
törzsmókus

@törzsmókus没有特定语言。语法是非常接近到C,OBJ-C,C ++,Java和许多其他语言,但如果你想真正的代码,你可能需要替换一个适当的操作,如||OR
卡雷布(Caleb)2015年

-1

递归strlen():一个相当无用的递归:

size_t strlen( const char* str )
{
    if( *str == 0 ) {
       return 0;
    }
    return 1 + strlen( str + 1 );
}

没有数学-一个非常简单的功能。当然,您不必在现实生活中以递归方式实现它,但这是一个很好的递归演示。


-2

学生可能涉及的另一个现实世界的递归问题是构建自己的网络爬虫,该爬虫从网站中提取信息并遵循该网站中的所有链接(以及这些链接中的所有链接,等等)。


2
与传统意义上的递归相比,通常由进程队列更好地解决。
蓬松的

-2

我使用递归程序解决了骑士模式(在棋board上)的问题。您应该移动骑士,以使骑士触及除几个标记正方形之外的每个正方形。

您只需:

mark your "Current" square
gather a list of free squares that are valid moves
are there no valid moves?
    are all squares marked?
        you win!
for each free square
    recurse!
clear the mark on your current square.
return.    

通过在这样的树中测试未来的可能性,可以处理多种“超前思考”方案。

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.