外行人的术语是使用PHP的递归函数

时间:2010-04-15 21:05:32

标签: php function recursion

任何人都可以通过外行语言和使用示例向PHP解释一个递归函数(不使用Fibonacci)吗?我正在看一个例子,但斐波那契完全失去了我!

提前谢谢;-) 您还经常在Web开发中使用它们吗?

18 个答案:

答案 0 :(得分:87)

Laymens术语:

递归函数是一个调用本身的函数

更深入一点:

如果该功能一直在调用,它如何知道何时停止?您设置了一个条件,称为基本案例。基本情况告诉我们递归调用何时停止,否则它将无限循环。

对我来说,什么是一个很好的学习例子,因为我有很强的数学背景,是factorial。根据下面的评论,似乎阶乘函数可能有点过多,我会留在这里以防你想要它。

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,但概念是相同的,

答案 1 :(得分:30)

一个例子是打印给定目录的任何子目录中的每个文件(如果这些目录中没有符号链接,可能会以某种方式破坏该功能)。打印所有文件的伪代码如下所示:

function printAllFiles($dir) {
    foreach (getAllDirectories($dir) as $f) {
        printAllFiles($f); // here is the recursive call
    }
    foreach (getAllFiles($dir) as $f) {
        echo $f;
    }
}

我们的想法是首先打印所有子目录,然后打印当前目录的文件。这个想法应用于所有子目录,这就是为所有子目录递归调用此函数的原因。

如果您想尝试此示例,则必须检查特殊目录...,否则您将始终无法调用printAllFiles(".")。此外,您必须检查要打印的内容以及当前的工作目录(请参阅opendir()getcwd(),...)。

答案 2 :(得分:22)

递归是重复的事情。就像一个从内部调用自身的函数。让我演示一个有点伪示例:

想象一下,你和你的朋友一起喝啤酒,但如果你不在午夜之前回家,你的妻子会给你地狱。为此,让我们创建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 :(
    }
}

现在让我们只是希望你不能喝足够的啤酒让你陶醉,以致你的妻子会让你在沙发上睡觉而不管是否准时回家 - .-

但是,这几乎就是递归的方式。

答案 3 :(得分:9)

它是一个自称的函数。它可用于处理重复自身的某些数据结构,例如树。 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));

理解递归的一种好方法是在运行时逐步执行代码。

答案 4 :(得分:7)

简单地说:递归函数是一个自我调用的函数。

答案 5 :(得分:5)

当一个函数调用自己完成一个未定义和有限时间的任务时,它非常简单。我自己的代码中的一个示例,用于填充多级类别树的函数

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

答案 6 :(得分:4)

递归是一种奇特的说法“在完成之前再做一次”。

要做的两件重要事情:

  1. 一个基础案例 - 你有一个目标要去。
  2. 测试 - 如何知道你是否已经到达目的地。
  3. 想象一个简单的任务:按字母顺序排列一堆书。一个简单的过程就是先读两本书,然后对它们进行排序。现在,这里有递归部分:还有更多的书吗?如果是这样,再做一次。 “再做一次”是递归。 “还有更多的书籍”是考验。并且“不,不再有书”是基本案例。

答案 7 :(得分:2)

当我得知自己在这里时,我发现了最好的解释:http://www.elated.com/articles/php-recursive-functions/

因为有一件事:

在内存中创建调用的函数(创建新实例)

所以递归函数不是要自己调用,而是调用其他实例 - 所以它在内存中不是一个函数做一些魔术。它在内存中的几个实例正在返回一些值 - 当例如函数a调用函数b时,这种行为是相同的。你有两个实例,如果递归函数称为自身的新实例。

尝试用纸上的实例绘制内存 - 这是有意义的。

答案 8 :(得分:1)

基本上这个。它一直在调用自己直到完成

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

答案 9 :(得分:1)

递归是循环的替代方案,它们很少会为您的代码带来更清晰或优雅。 Progman的回答给出了一个很好的例子,如果他不使用递归,他将被迫跟踪他当前在哪个目录(这称为状态)递归允许他使用堆栈进行簿记(变量所在的区域)并存储方法的返回地址)

标准示例factorial和Fibonacci对于理解概念没有用,因为它们很容易被循环替换。

答案 10 :(得分:1)

这是一个实际的例子(已经有几个好的例子)。我只想添加一个对几乎所有开发人员都有用的内容。

在某些时候,开发人员需要解析一个对象,就像API或某种类型的对象或数组的响应一样。

这个函数最初被调用来解析一个可能只包含参数的对象,但是如果该对象还包含其他对象或数组呢?这将需要解决,并且大多数情况下基本函数已经执行此操作,因此函数再次调用自身(在确认键或值是对象或数组之后)并解析此新对象或数组。最终返回的是一个字符串,它为了便于阅读而在一行上创建每个参数,但您可以轻松地将值记录到日志文件或插入到数据库或其他任何内容中。

我添加了$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”] =&gt; object(stdClass)#2(6){[“fName”] =&gt; string(4)“Bill”[“lName”] =&gt; string(4)“Test”[“address1”] =&gt; string(18)“22 S. Deleware St.” [ “城市”] =&GT; string(8)“Chandler”[“state”] =&gt; string(2)“AZ”[“zip”] =&gt; int(85225)} [“phone”] =&gt; string(12)“602-312-4455”[“transactionDetails”] =&gt; array(4){[“totalAmt”] =&gt; string(6)“100.00”[“currency”] =&gt; string(3)“USD”[“tax”] =&gt; string(4)“5.00”[“shipping”] =&gt; string(4)“5.00”} [“item”] =&gt; object(stdClass)#3(3){[“name”] =&gt; string(7)“T-shirt”[“color”] =&gt; string(4)“blue”[“itemQty”] =&gt; 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

我做了嵌套切换语句以避免与if . . . ifelse . . . else混淆,但它几乎一样长。如果它有帮助,只需询问if条件,我可以将它们粘贴给需要它的人。

答案 11 :(得分:0)

<块引用>

当某物包含或使用相似版本时发生递归 本身。当专门谈论计算机编程时, 递归发生在函数调用自身时。

以下链接帮助我更好地理解递归,即使我认为这是迄今为止我学到的最好的。

通过一个例子来理解内部(递归)函数在执行时是如何调用的。

请仔细阅读文章,我已经粘贴了测试程序,以防文章被丢弃。您可以在本地服务器上运行该程序以查看其运行情况。

https://www.elated.com/php-recursive-functions/

<?php

function factorial( $n ) {

  // Base case
  if ( $n == 0 ) {
    echo "Base case: $n = 0. Returning 1...<br>";
    return 1;
  }

  // Recursion
  echo "$n = $n: Computing $n * factorial( " . ($n-1) . " )...<br>";
  $result = ( $n * factorial( $n-1 ) );
  echo "Result of $n * factorial( " . ($n-1) . " ) = $result. Returning $result...<br>";
  return $result;
}

echo "The factorial of 5 is: " . factorial( 5 );

?>

答案 12 :(得分:0)

我也不认为示例有帮助。当涉及数学时,您无法立即解决两个问题。说话足够

示例:

我有一个函数,可以根据:分隔符将字符串分解为数组。

public function explodeString($string)
{
  return explode(":", $string);
}

我还有另一个函数,我将字符串作为输入

public function doRec()
{
    $strings = [
        'no:go',
        'hello:world',
        'nested' => [
            'iam:good',
            'bad:array',
            'bad:how',
            'bad:about',
        ]
    ];

    $response = [];

    foreach ($strings as $string) {
        array_push($response,$this->explodeString($string));
    }

    return $response;
}

问题是,我的输入具有嵌套数组,而我的explodeString函数接收的类型为string。我可以在explodeString函数中重写一些代码以适应此问题,但是我仍然需要相同的函数对字符串进行相同的操作。那就是我可以在其中调用方法recursively的地方。因此,这是带有递归的最后一个explodeString函数。

public function explodeString($string)
{
    if (is_array($string)) {
       $combine = [];
       foreach ($string as $str) {
           array_push($combine, $this->explodeString($str));
        }
       return $combine;
    }

    return explode(":", $string);
}

答案 13 :(得分:0)

用于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位数的输入,至少有两个不同的数字。

答案 14 :(得分:0)

它是一个简单的递归(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);

 ?>

答案 15 :(得分:0)

这是使用递归的阶乘的一个非常简单的例子:

因子是一个非常简单的数学概念。他们写得像5!这意味着5 * 4 * 3 * 2 * 1.所以6!是720和4!是24岁。

function factorial($number) { 

    if ($number < 2) { 
        return 1; 
    } else { 
        return ($number * factorial($number-1)); 
    } 
}

希望这对你有用。 :)

答案 16 :(得分:0)

如果你给Anthony Forloney的例子添加一定的价值(例如,&#34; 1&#34;),一切都会很清楚:

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.
  }
}

答案 17 :(得分:0)

遍历目录树就是一个很好的例子。您可以执行类似处理数组的操作。这是一个非常简单的递归函数,它简单地处理一个字符串,一个简单的字符串数组,或任何深度的字符串嵌套数组,替换&#39; hello&#39;与...再见&#39;在字符串或数组或任何子数组的值:

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
}

它知道何时退出,因为在某些时候,&#34;&#34;&#34;它处理的不是数组。例如,如果您调用replaceHello(&#39; hello&#39;),它将返回&#39;再见&#39;。如果你发送一个字符串数组,虽然它会为数组的每个成员调用一次,然后返回处理过的数组。