谁能用外行语言和示例以PHP(不使用Fibonacci)向我解释递归函数?我只是在看一个例子,但斐波那契完全让我迷失了!

预先感谢;-)
您还经常在Web开发中使用它们吗?

评论

阅读stackoverflow.com/questions/2648968,这最终将变得有意义。

或尝试使用Google的“您要说的话”链接:google.com/search?hl=zh-CN&q=recursion

@kevin也许您应该将注释写为“ duplicate stackoverflow.com/questions/2648968”;)

@Kevin:但是你忘了基本情况!他不会那样学习递归,他会一直单击它,直到他的堆栈溢出为止。 :(

鉴于我们所在的网站,我认为@camccann合适

#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位数字,并且至少要有两个不同的数字。