用外行的话来说就是使用 PHP 的递归函数

有没有人能用外行语言和例子,用 PHP (不用斐波那契语)给我解释一个递归函数?我正在看一个例子,但斐波那契完全失去了我!

先谢谢你; -) 另外,你在网页开发中多久使用一次它们?

118594 次浏览

外行术语:

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

再深入一点:

如果函数一直调用自己,它如何知道何时停止?你设置了一个条件,叫做基本情况。基本情况告诉递归调用何时停止,否则它将无限循环。

对我来说,阶乘是一个很好的学习例子,因为我有很强的数学背景。通过下面的注释,似乎似乎阶乘函数有点太多了,我将把它留在这里,以防您需要它。

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 的概念是一样的,

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

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

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(),...)。

基本上就是这样,它一直自称,直到完成

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

你也可以尝试谷歌一下。注意“你的意思是”(点击它...)

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

标准示例阶乘和斐波那契对于理解这个概念没有用处,因为它们很容易被循环替换。

递归是一种花哨的说法,意思是“在完成之前再做一遍”。

有两样重要的东西:

  1. 一个基本的情况-你有一个目标要达到。
  2. A test-How to know if you’ve got to where you’re going。

想象一个简单的任务: 按字母顺序排序一堆书。一个简单的过程就是把前两本书分类。现在,递归部分来了: 还有更多的书吗?如果是这样,再来一次。“再做一次”就是递归。“还有没有更多的书”就是测试。“不,不要再看书了”是基本情况。

它是一个调用自身的函数。它对于遍历重复自身的特定数据结构(如树)非常有用。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));

理解递归的一个很好的方法是在运行时逐步完成代码。

递归是一种自我重复的东西。就像一个从内部调用自己的函数。让我用一个有点虚假的例子来演示:

想象一下你和你的朋友们出去喝啤酒,但是如果你在午夜之前不回家,你的妻子会把你打得落花流水。为此,让我们创建 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 :(
}
}

现在,让我们只希望你没有喝足够的啤酒变得如此醉,以至于你的妻子会让你睡在沙发上,而不管你是否准时回家。-

但没错,递归就是这样的。

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

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

遍历目录树就是一个很好的例子。您可以执行类似于处理数组的操作。这里有一个非常简单的递归函数,它只处理一个字符串,一个简单的字符串数组,或者任何深度的嵌套字符串数组,将字符串中的“ hello”实例替换为“ bye”,或者将数组或任何子数组的值替换为“ 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’) ,它将返回‘ bye’。如果您向它发送一个字符串数组,虽然它会对数组中的每个成员调用自己一次,但是会返回经过处理的数组。

如果你给 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.
}
}

下面是一个实际的例子(已经有几个很好的例子了)。我只是想添加一个对几乎所有开发人员都有用的工具。

在某种程度上,开发人员将需要像解析来自 API 或某种类型的对象或数组的响应那样解析对象。

最初调用这个函数是为了解析一个可能只包含参数的对象,但是如果该对象也包含其他对象或数组该怎么办?这个问题需要解决,在大多数情况下,基本函数已经解决了这个问题,所以函数只是再次调用自己(在确认键或值是对象或数组之后)并解析这个新对象或数组。最终返回的是一个字符串,该字符串在一行中单独创建每个参数以提高可读性,但是您也可以轻松地将这些值记录到日志文件或插入到数据库或其他内容中。

我添加了 $prefix参数,以使用父元素来帮助描述 end 变量,这样我们就可以看到这个值属于什么。它不包括诸如 null 值之类的东西,但是可以从这个示例中进行修改。

如果你有目标:

$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 S.。[“ city”] = > string (8)“ Chandler”[“ state”] = > string (2)“ AZ”[“ zip”] = > int (85225)}[“ phone”] = > string (12)“602-312-4455”[“ transactionDetails”] = >数组(4){[“ total Amt”] = > string (6)“100.00”[“ currency”] = > string (3)“ USD”[“ tax”] = > string (4)“5.00”[“ Shipping”] = > string (4)“5.00”}{[“ total Amt”] = > string (6)“100.00”[“ currency”] = > string (3)“ USD”[“ tax”] = > string (4)“5.00”[“ Shipping”] = > string (4)“5.00”}[“ item”] = > object (stdClass) # 3(3){[“ name”] = > string (7)“ T 恤”[“ color”] = > string (4)“ blue”[“ 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

我做了 嵌套开关语句,以避免与 if . . . ifelse . . . else混淆,但它几乎一样长。如果有帮助的话,只需要询问 If 条件句,我就可以把它们粘贴到那些需要它的人身上。

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

阶乘是一个非常简单的数学概念。他们写得像5!这意味着5 * 4 * 3 * 2 * 1。那么6号!是720和4!24岁。

function factorial($number) {


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

希望这对你有用。 :)

它的工作原理是一个简单的递归示例(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);


?>

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

这是因为一件事:

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

所以递归函数 并不是自称,但是它调用其他实例-所以它不是内存中的一个函数。它在内存中的两个实例返回自己的一些值——这种行为在函数 a 调用函数 b 时是相同的。您有两个实例,以及如果递归函数调用本身的新实例。

试着用纸上的实例画出记忆——这将是有意义的。

用于 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 常数的人来说,它需要一个至少有两个不同数字的4位输入。

当某个内容包含或使用类似的版本时,就会发生递归 当谈到计算机编程时, 当函数调用自身时,就会发生递归。

下面的链接帮助我更好地理解递归,甚至我认为这是目前为止我所学到的最好的。

它附带了一个示例来理解内部(递归)函数在执行时如何调用。

请仔细阅读这篇文章,我已经粘贴了测试程序,以防文章被删除。您可以在本地服务器上运行该程序来查看它的运行情况。

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 );


?>