【发布时间】:2010-09-12 17:01:03
【问题描述】:
任何人都可以建议说明递归函数的编程示例吗?有常见的老马,例如 斐波那契数列 和 河内塔,但除此之外的任何东西都会很有趣.
【问题讨论】:
-
查看我对这个问题的评论:stackoverflow.com/questions/126756/…
标签: recursion
任何人都可以建议说明递归函数的编程示例吗?有常见的老马,例如 斐波那契数列 和 河内塔,但除此之外的任何东西都会很有趣.
【问题讨论】:
标签: recursion
This illustration 是英文,而不是实际的编程语言,但有助于以非技术方式解释该过程:
一个孩子睡不着,所以她妈妈讲了一个小青蛙的故事, 谁睡不着,所以青蛙的妈妈讲了一个小熊的故事, 谁睡不着,所以熊妈妈讲了一个小黄鼠狼的故事 ……谁睡着了。 ……小熊睡着了; ……小青蛙睡着了; ……孩子睡着了。【讨论】:
要想understandrecursion,首先要了解recursion。
【讨论】:
递归的经验法则是,“使用递归,当且仅当您的任务在每次迭代中分成两个或更多类似的任务”。
所以斐波那契不是递归应用的好例子,而河内是一个很好的例子。
所以大多数递归的好例子都是在不同的disquises中遍历树。
例如: 图遍历 - 访问过的节点永远不会被再次访问的要求有效地使图成为树(树是没有简单循环的连通图)
分治算法(快速排序、归并排序)——“分”之后的部分构成子节点,“征服”构成从父节点到子节点的边。
【讨论】:
测试一个字符串是否为回文怎么样?
bool isPalindrome(char* s, int len)
{
if(len < 2)
return TRUE;
else
return s[0] == s[len-1] && isPalindrome(&s[1], len-2);
}
当然,您可以使用循环更有效地做到这一点。
【讨论】:
constexpr bool compileTimeIsPalindrome(const char* s, int len) { return len < 2 ? true : s[0] == s[len-1] && compileTimeIsPalindrome(&s[1], len-2); } ;-)
【讨论】:
来自数学世界,有the Ackermann function:
Ackermann(m, n)
{
if(m==0)
return n+1;
else if(m>0 && n==0)
return Ackermann(m-1, 1);
else if(m>0 && n>0)
return Ackermann(m-1, Ackermann(m, n-1));
else
throw exception; //not defined for negative m or n
}
它总是终止,但即使对于非常小的输入,它也会产生非常大的结果。例如,Ackermann(4, 2) 返回 265536 - 3。
【讨论】:
if (m > 0 && n == 0) 它应该返回Ackermann(m - 1, 1) 而不是Ackermann(m - 1, n)(即Ackermann(m - 1, 0))。至少根据你引用的维基百科。
另外两个“通常的嫌疑人”是Quicksort 和 MergeSort
【讨论】:
interpreter design pattern 是一个很好的例子,因为很多人没有发现递归。维基百科文章中列出的示例代码很好地说明了如何应用它。但是,仍然实现解释器模式的更基本的方法是用于嵌套列表的 ToString 函数:
class List {
public List(params object[] items) {
foreach (object o in items)
this.Add(o);
}
// Most of the implementation omitted …
public override string ToString() {
var ret = new StringBuilder();
ret.Append("( ");
foreach (object o in this) {
ret.Append(o);
ret.Append(" ");
}
ret.Append(")");
return ret.ToString();
}
}
var lst = new List(1, 2, new List(3, 4), new List(new List(5), 6), 7);
Console.WriteLine(lst);
// yields:
// ( 1 2 ( 3 4 ) ( ( 5 ) 6 ) 7 )
(是的,我知道如果您希望有一个名为Eval 的函数,那么在上面的代码中发现解释器模式并不容易……但实际上,解释器模式并没有告诉我们这个函数被调用了什么,甚至它是什么确实如此,并且 GoF 书中明确列出了上述模式的示例。)
【讨论】:
fold 和 fold 可以反过来解释(双关语)作为编程语言的参数化解释器,只需两个命令element(e) 和NIL,其中列表是程序。酷吧?在这里找到解释:Functional Programming for Beginners - Rúnar Óli Bjarnason - Boston Area Scala Enthusiasts.
在我看来,了解递归是件好事,但大多数可以使用递归的解决方案也可以使用迭代来完成,而且迭代的效率要高得多。
这里说的是在嵌套树(如 ASP.NET 或 Winforms)中查找控件的递归方式:
public Control FindControl(Control startControl, string id)
{
if (startControl.Id == id)
return startControl
if (startControl.Children.Count > 0)
{
foreach (Control c in startControl.Children)
{
return FindControl(c, id);
}
}
return null;
}
【讨论】:
这是一个来自文件系统世界的实用示例。此实用程序递归地计算指定目录下的文件。 (我不记得为什么了,但实际上我很久以前就需要这样的东西......)
public static int countFiles(File f) {
if (f.isFile()){
return 1;
}
// Count children & recurse into subdirs:
int count = 0;
File[] files = f.listFiles();
for (File fileOrDir : files) {
count += countFiles(fileOrDir);
}
return count;
}
(请注意,在 Java 中,File 实例可以表示普通文件或目录。此实用程序将目录排除在计数之外。)
一个常见的现实世界示例是FileUtils.deleteDirectory() 来自 Commons IO 库;见API doc & source。
【讨论】:
一个真实的例子是“物料清单成本”问题。
假设我们有一家制造最终产品的制造公司。每个产品都可以通过其部件列表和组装这些部件所需的时间来描述。例如,我们用外壳、电机、卡盘、开关和电线制造手持电钻,需要 5 分钟。
给定每分钟的标准人工成本,制造我们每件产品的成本是多少?
哦,顺便说一句,有些零件(例如电源线)是购买的,所以我们直接知道它们的成本。
但实际上我们自己制造了一些零件。我们用一个外壳、一个定子、一个转子、一个轴和轴承制作了一个电机,这需要 15 分钟。
我们用冲压件和电线制造定子和转子,...
因此,确定成品的成本实际上相当于遍历表示我们流程中所有整体到零件列表关系的树。用递归算法很好地表达了这一点。当然也可以迭代完成,但核心思想与自己动手记账混在一起,所以不清楚发生了什么。
【讨论】:
我知道的最毛茸茸的例子是 Knuth 的 Man or Boy Test。 除了递归之外,它还使用嵌套函数定义(闭包)、函数引用和常量/函数二元论(按名称调用)的 Algol 特性。
【讨论】:
正如其他人已经说过的,很多规范递归示例都是学术性的。
我过去遇到的一些实际用途是:
1 - 导航树结构,例如文件系统或注册表
2 - 操作可能包含其他容器控件(如 Panels 或 GroupBoxes)的容器控件
【讨论】:
我个人最喜欢的是Binary Search
编辑:另外,树遍历。例如,遍历文件夹文件结构。
【讨论】:
Guido van Rossum 的Implementing Graphs 在 Python 中有一些递归函数来查找图中两个节点之间的路径。
【讨论】:
我最喜欢的类型,Merge Sort
(最喜欢的,因为我记得算法并且在性能方面还不算太差)
【讨论】:
【讨论】:
反转一个字符串怎么样?
void rev(string s) {
if (!s.empty()) {
rev(s[1..s.length]);
}
print(s[0]);
}
理解这一点有助于理解递归。
【讨论】:
processing lists 怎么样,比如:
【讨论】:
曾几何时,不久前,小学生使用 Logo 和 Turtle Graphics 学习递归。 http://en.wikipedia.org/wiki/Turtle_graphics
递归也非常适合通过详尽的试验来解决难题。有一种叫做“填充”(Google it)的谜题,你会得到一个像填字游戏一样的网格,还有单词,但没有线索,没有编号的方块。我曾经为一个谜题发布者编写了一个使用递归的程序来解决谜题,以确保已知的解决方案是唯一的。
【讨论】:
递归函数非常适合使用recursively defined datatypes:
等等
【讨论】:
将电子表格列索引转换为列名。
这比听起来要复杂,因为电子表格列不能正确处理“0”数字。例如,如果在从 Z 递增到 AA 时将 A-Z 作为数字,这就像从 9 到 11 或从 9 到 00 而不是 10(取决于 A 是 1 还是 0)。即使是Microsoft Support example 也会因为任何高于 AAA 的东西而出错!
递归解决方案有效,因为您可以在那些新数字边界上进行递归。此实现在 VB.Net 中,并将第一列 ('A') 视为索引 1。
Function ColumnName(ByVal index As Integer) As String
Static chars() As Char = {"A"c, "B"c, "C"c, "D"c, "E"c, "F"c, "G"c, "H"c, "I"c, "J"c, "K"c, "L"c, "M"c, "N"c, "O"c, "P"c, "Q"c, "R"c, "S"c, "T"c, "U"c, "V"c, "W"c, "X"c, "Y"c, "Z"c}
index -= 1 'adjust index so it matches 0-indexed array rather than 1-indexed column'
Dim quotient As Integer = index \ 26 'normal / operator rounds. \ does integer division'
If quotient > 0 Then
Return ColumnName(quotient) & chars(index Mod 26)
Else
Return chars(index Mod 26)
End If
End Function
【讨论】: