预先感谢;-)
您还经常在Web开发中使用它们吗?
#1 楼
Laymens的术语:递归函数是调用自身的函数
更深入:
如果该函数不断调用自身,它如何知道何时停止?您设置了一个条件,称为基本情况。基本案例告诉我们递归调用何时停止,否则它将无限循环。
对我来说,一个很好的学习示例,因为我在数学方面有很强的背景,因此是阶乘。通过下面的评论,似乎阶乘函数可能有点过多,我将其保留在这里,以防万一您想要它。
function fact($n) {
if ($n === 0) { // our base case
return 1;
}
else {
return $n * fact($n-1); // <--calling itself.
}
}
关于在Web开发中使用递归函数,我不要亲自使用递归调用。并不是说依赖于递归是不好的做法,但是它们不应该是您的首选。如果使用不当,它们可能会致命。
尽管我无法与目录示例竞争,但我希望这会有所帮助。
(4/20/10)更新:
它也会有所帮助查看此问题,其中公认的答案以通俗易懂的方式演示了递归函数的工作原理。即使OP的问题与Java有关,但概念是相同的,
了解基本递归
评论
尽管您的示例很好地说明了该示例,但从外行角度来讲,几乎不能认为该示例是一个示例。如果OP在基于Fibonacci示例的情况下理解递归时遇到一些麻烦,我认为阶乘也不会削减它。特别是涉及这些数学定义。
–体面的涉水者
2010-4-15在21:39
@ fireeyedboy,stereofrog:在大学里,我学会递归时要学习阶乘函数,这让我点击了。我和OP可能有不同的数学背景,但是为了简洁起见,我省略了阶乘定义,并强调了递归的递归定义。
–安东尼·弗洛尼(Anthony Forloney)
2010-4-15在22:29
不需要为函数定义一个结束条件(基本情况),这会使您的定义有些不正确。 (尽管在PHP中,您必须否则会导致堆栈溢出)
– Yacoby
2010-4-15在22:32
#2 楼
一个示例是在给定目录的任何子目录中打印每个文件(如果这些目录中没有符号链接,则可能以某种方式破坏该功能)。打印所有文件的伪代码如下所示:function printAllFiles($dir) {
foreach (getAllDirectories($dir) as $f) {
printAllFiles($f); // here is the recursive call
}
foreach (getAllFiles($dir) as $f) {
echo $f;
}
}
其思想是先打印所有子目录,然后再打印当前目录的文件。这个想法适用于所有子目录,这就是为所有子目录递归调用此函数的原因。
如果要尝试此示例,必须检查特殊目录
.
和..
,否则,您将始终无法拨打电话。另外,您必须检查要打印的内容以及当前的工作目录(请参阅printAllFiles(".")
,opendir()
,...)。评论
我喜欢这个答案而不是阶乘答案,因为它确实适用。实际需要多少人来计算阶乘,以及为什么无论如何都要递归进行阶乘(也许是记忆化,但在那一点上,为什么不只是从一开始就使用查找表)。
– davidtbernal
2010年4月15日在21:50
#3 楼
递归是重复的。就像从内部调用自身的函数一样。让我用一个伪造的示例进行演示:想象一下,您与好友一起喝啤酒,但是如果您在午夜之前不回家,您的妻子就会让您下地狱。为此,让我们创建orderAndDrinkBeer($ time)函数,其中$ time为午夜减去您喝完当前饮料并回家所需的时间。
因此,到达酒吧后,您就可以订购您的第一杯啤酒开始喝酒:
$timeToGoHome = '23'; // Let's give ourselves an hour for last call and getting home
function orderAndDrinkBeer($timeToGoHome) { // Let's create the function that's going to call itself.
$beer = New Beer(); // Let's grab ourselves a new beer
$currentTime = date('G'); // Current hour in 24-hour format
while ($beer->status != 'empty') { // Time to commence the drinking loop
$beer->drink(); // Take a sip or two of the beer(or chug if that's your preference)
}
// Now we're out of the drinking loop and ready for a new beer
if ($currentTime < $timeToGoHome) { // BUT only if we got the time
orderAndDrinkBeer($timeToGoHome); // So we make the function call itself again!
} else { // Aw, snap! It is time :S
break; // Let's go home :(
}
}
现在,让我们希望您不能喝足够多的啤酒来陶醉,以至于您的妻子会让您陶醉不论准时回家,都可以在沙发上睡觉-.-
但是,这就是递归的方式。
评论
10年后的今天,这个答案已成为我的梦想!
–专业
12月7日10:27
#4 楼
它是一个自我调用的函数。它对于遍历某些重复的数据结构(例如树)很有用。 HTML DOM是一个经典示例。javascript中的树结构示例,以及用于“遍历”树的递归函数。
1
/ \
2 3
/ \
4 5
----
var tree = {
id: 1,
left: {
id: 2,
left: null,
right: null
},
right: {
id: 3,
left: {
id: 4,
left: null,
right: null
},
right: {
id: 5,
left: null,
right: null
}
}
};
要遍历树,我们反复调用相同的函数,将当前节点的子节点传递给相同的函数。然后,我们再次调用该函数,首先在左侧节点上,然后在右侧上。
在此示例中,我们将获得树的最大深度
var depth = 0;
function walkTree(node, i) {
//Increment our depth counter and check
i++;
if (i > depth) depth = i;
//call this function again for each of the branch nodes (recursion!)
if (node.left != null) walkTree(node.left, i);
if (node.right != null) walkTree(node.right, i);
//Decrement our depth counter before going back up the call stack
i--;
}
最后我们调用函数
alert('Tree depth:' + walkTree(tree, 0));
理解递归的一种很好的方法是在运行时逐步遍历代码。
#5 楼
简而言之:递归函数是一个调用自身的函数。#6 楼
很简单,当一个函数调用自己以完成一段不确定的有限时间的任务时。我自己的代码中的一个示例,该函数用于填充具有多级类别树的function category_tree($parent=0,$sep='') { $q="select id,name from categorye where parent_id=".$parent; $rs=mysql_query($q); while($rd=mysql_fetch_object($rs)) { echo('id.'">'.$sep.$rd->name.''); category_tree($rd->id,$sep.'--'); } }
#7 楼
递归是一种“重复做直到完成”的奇特方法。有两个重要的事情:
一个基本案例-您已经有一个目标要实现。
测试-如何知道您是否要去往哪里。
想象一个简单的任务:按字母顺序对一堆书进行排序。一个简单的过程就是将前两本书整理一下。现在,这是递归部分:还有更多书吗?如果是这样,请再做一次。 “再次执行”是递归。 “还有书吗”就是考验。而“没有,没有更多的书”是基本情况。
#8 楼
当我了解自己的时候,我发现了最好的解释:http://www.elated.com/articles/php-recursive-functions/这是因为一件事:
被调用的函数在内存中创建(创建了新实例)
因此递归函数不是调用自身,而是调用其他实例-因此它不是内存中的一个函数在执行某些操作魔法。它在内存中的几个实例正在返回自己的一些值-例如,当函数a调用函数b时,此行为是相同的。您有两个实例,以及递归函数是否称为自身的新实例。
尝试在纸上绘制带有实例的内存-这样做很有意义。
#9 楼
递归是循环的一种替代方法,很少使循环为您的代码带来更多的清晰度。 Progman的答案给出了一个很好的例子,如果他不使用递归,那么他将被迫跟踪他当前所在的目录(称为状态),递归允许他使用堆栈(变量所在的区域)进行簿记。并存储方法的返回地址)阶乘和斐波那契标准示例对于理解该概念没有用,因为它们很容易被循环替换。
#10 楼
基本上是这样。它会不断自我调用直到完成void print_folder(string root)
{
Console.WriteLine(root);
foreach(var folder in Directory.GetDirectories(root))
{
print_folder(folder);
}
}
还可以使用循环!
void pretend_loop(int c)
{
if(c==0) return;
print "hi";
pretend_loop(c-);
}
您也可以尝试谷歌搜索。请注意“您的意思是您的意思”(单击它...)。 http://www.google.com/search?q=recursion&spell=1
评论
错字...(第一个函数的参数列表的结尾括号在哪里?)
– Ponkadoodle
2010-4-15在22:17
#11 楼
这是一个实际的例子(已经有好几个例子了)。我只想添加一个对几乎所有开发人员都有用的功能。有时,开发人员将需要解析对象,就像API或某种类型的对象或数组的响应一样。
最初调用此函数来分析可能仅包含参数的对象,但是如果该对象还包含其他对象或数组怎么办?这将需要解决,并且在大多数情况下,基本功能已经做到了,因此该功能只是再次调用自身(在确认键或值是对象还是数组之后)并解析此新对象或数组。最终返回的是一个字符串,该字符串可以自己在一行上创建每个参数以提高可读性,但是您可以轻松地将这些值记录到日志文件中或插入到DB中或其他任何内容。
我添加了
$prefix
参数可使用父元素来帮助描述结束变量,以便我们可以看到该值的含义。它不包含空值之类的东西,但是可以通过此示例进行修改。如果您有对象:
$apiReturn = new stdClass();
$apiReturn->shippingInfo = new stdClass();
$apiReturn->shippingInfo->fName = "Bill";
$apiReturn->shippingInfo->lName = "Test";
$apiReturn->shippingInfo->address1 = "22 S. Deleware St.";
$apiReturn->shippingInfo->city = "Chandler";
$apiReturn->shippingInfo->state = "AZ";
$apiReturn->shippingInfo->zip = 85225;
$apiReturn->phone = "602-312-4455";
$apiReturn->transactionDetails = array(
"totalAmt" => "100.00",
"currency" => "USD",
"tax" => "5.00",
"shipping" => "5.00"
);
$apiReturn->item = new stdClass();
$apiReturn->item->name = "T-shirt";
$apiReturn->item->color = "blue";
$apiReturn->item->itemQty = 1;
并使用:
var_dump($apiReturn);
它将返回:
object(stdClass)#1(4){[“” shippingInfo“ ] => object(stdClass)#2(6){[“” fName“] => string(4)” Bill“ [” lName“] => string(4)” Test“ [” address1“] => string( 18)“ 22 S. Deleware St.” [“ city”] =>字符串(8)“ Chandler” [“ state”] =>字符串(2)“ AZ” [“ zip”] => int(85225)} [“ phone”] =>字符串(12 )“ 602-312-4455” [“ transactionDetails”] => array(4){[“” totalAmt“] => string(6)” 100.00“ [” currency“] => string(3)” USD“ [”税“] => string(4)” 5.00“ [” shipping“] => string(4)” 5.00“} [” item“] => object(stdClass)#3(3){[” name“] = >字符串(7)“ T恤” [“颜色”] =>字符串(4)“蓝色” [“ itemQty”] => int(1)}}
和这是将其解析为带有每个参数的换行符的字符串的代码:
function parseObj($obj, $prefix = ''){
$stringRtrn = '';
foreach($obj as $key=>$value){
if($prefix){
switch ($key) {
case is_array($key):
foreach($key as $k=>$v){
$stringRtrn .= parseObj($key, $obj);
}
break;
case is_object($key):
$stringRtrn .= parseObj($key, $obj);
break;
default:
switch ($value) {
case is_array($value):
$stringRtrn .= parseObj($value, $key);
break;
case is_object($value):
$stringRtrn .= parseObj($value, $key);
break;
default:
$stringRtrn .= $prefix ."_". $key ." = ". $value ."<br>";
break;
}
break;
}
} else { // end if($prefix)
switch($key){
case is_array($key):
$stringRtrn .= parseObj($key, $obj);
break;
case is_object($key):
$stringRtrn .= parseObj($key, $obj);
break;
default:
switch ($value) {
case is_array($value):
$stringRtrn .= parseObj($value, $key);
break;
case is_object($value):
$stringRtrn .= parseObj($value, $key);
break;
default:
$stringRtrn .= $key ." = ". $value ."<br>";
break;
} // end inner switch
} // end outer switch
} // end else
} // end foreach($obj as $key=>$value)
return $stringRtrn;
} // END parseObj()
这将返回对象,如下所示:
shippingInfo_fName = Bill
shippingInfo_lName = Test
shippingInfo_address1 = 22 S. Deleware St.
shippingInfo_city = Chandler
shippingInfo_state = AZ
shippingInfo_zip = 85225
phone = 602-312-4455
transactionDetails_totalAmt = 100.00
transactionDetails_currency = USD
transactionDetails_tax = 5.00
transactionDetails_shipping = 5.00
item_name = T-shirt
item_color = blue
item_itemQty = 1
我做了嵌套switch语句,以避免与
if . . . ifelse . . . else
混淆,但是几乎只要。如果有帮助,请询问是否有条件,我可以将它们粘贴给需要的人。#12 楼
遍历目录树是一个很好的例子。您可以执行类似的操作来处理数组。这是一个非常简单的递归函数,可以简单地处理字符串,简单的字符串数组或嵌套的任何深度的字符串数组,并将字符串中的“ hello”的实例替换为字符串中的“再见”或该数组或任何值子数组:function replaceHello($a) {
if (! is_array($a)) {
$a = str_replace('hello', 'goodbye', $a);
} else {
foreach($a as $key => $value) {
$a[$key] = replaceHello($value);
}
}
return $a
}
它知道何时退出,因为在某些时候,它正在处理的“事物”不是数组。例如,如果调用replaceHello('hello'),它将返回“再见”。如果向它发送一个字符串数组,尽管它将为该数组的每个成员调用一次自身,然后返回已处理的数组。
#13 楼
如果您在Anthony Forloney的示例中添加特定值(例如“ 1”),则所有内容都将是清楚的:function fact(1) {
if (1 === 0) { // our base case
return 1;
}
else {
return 1 * fact(1-1); // <--calling itself.
}
}
原始值:
function fact($n) {
if ($n === 0) { // our base case
return 1;
}
else {
return $n * fact($n-1); // <--calling itself.
}
}
评论
你如何阻止它?
– Pathros
16年7月28日在17:12
#14 楼
这是带有递归的阶乘的一个非常简单的示例:Factorials是一个非常简单的数学概念。他们写成5!这意味着5 * 4 * 3 * 2 *1。所以6!是720和4!是24。
function factorial($number) {
if ($number < 2) {
return 1;
} else {
return ($number * factorial($number-1));
}
}
希望对您有用。 :)
评论
你如何阻止它?
– Pathros
16年7月28日在17:11
在阶乘中,我们连续调用,直到传递的参数为1为止,并且当参数为1时,我们仅返回1即可停止递归
–user4614642
16年8月3日,12:34
#15 楼
它是一个简单的递归(Y)示例。<?php
function factorial($y,$x) {
if ($y < $x) {
echo $y;
} else {
echo $x;
factorial($y,$x+1);
}
}
$y=10;
$x=0;
factorial($y,$x);
?>
#16 楼
用于Kaprekar常数的递归function KaprekarsConstant($num, $count = 1) {
$input = str_split($num);
sort($input);
$ascendingInput = implode($input);
$descendingInput = implode(array_reverse($input));
$result = $ascendingInput > $descendingInput
? $ascendingInput - $descendingInput
: $descendingInput - $ascendingInput;
if ($result != 6174) {
return KaprekarsConstant(sprintf('%04d', $result), $count + 1);
}
return $count;
}
函数不断调用自身,直到计算结果达到Kaprekars常数为止。
/ edit对于不知道Kaprekars Constant的任何人,它需要输入4位数字,并且至少要有两个不同的数字。
评论
阅读stackoverflow.com/questions/2648968,这最终将变得有意义。或尝试使用Google的“您要说的话”链接:google.com/search?hl=zh-CN&q=recursion
@kevin也许您应该将注释写为“ duplicate stackoverflow.com/questions/2648968”;)
@Kevin:但是你忘了基本情况!他不会那样学习递归,他会一直单击它,直到他的堆栈溢出为止。 :(
鉴于我们所在的网站,我认为@camccann合适