递归函数可以有迭代/循环吗?


12

我一直在研究递归函数,很显然,它们是可以自我调用的函数,并且不使用迭代/循环(否则它将不是递归函数)。

但是,在网上浏览示例(8皇后递归问题)时,我发现此功能:

private boolean placeQueen(int rows, int queens, int n) {
    boolean result = false;
    if (row < n) {
        while ((queens[row] < n - 1) && !result) {
            queens[row]++;
            if (verify(row,queens,n)) {
                ok = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

while涉及到一个循环。

...所以我现在有点迷路了。我可以使用循环吗?


5
是否编译。是。那为什么要问?
Thomas Eding 2012年

6
递归的整个定义是,在某个点上,函数可以在返回之前作为其自身执行的一部分进行调用(无论是由自身调用还是由其调用的其他函数调用)。关于该定义的所有内容都没有排除循环的可能性。
cHao 2012年

作为cHao的补充,递归函数将在其更简单的版本上调用(否则,它将永远循环)。引用orbling(用简单的英语来说,递归是什么?):“递归编程是逐步减少问题以更轻松地解决其自身版本的过程。” 在这种情况下,最困难的版本placeQueen是“ 8位皇后”,而最简单的版本placeQueen是“ 7位皇后”(然后是6位,
Brian

您可以使用任何适用于Omega的产品。软件规范很少指定要使用哪种编程风格-除非您在学校并且作业如此说。
Apoorv Khurasia

@ThomasEding:是的,它当然可以编译和运行。但是我现在只是在学习工程学-在这一点上,对我来说重要的是严格的概念/定义,而不是当今程序员使用它的方式。所以我问我的概念是否正确(看来不正确)。
欧米茄

Answers:


41

您误解了递归:尽管可以使用它来代替迭代,但是绝对不需要递归函数本身不具有迭代。

将函数视为递归的唯一要求是存在直接或间接调用其自身的代码路径。所有正确的递归函数还具有某种条件,可以防止它们永远“递归”。

您的递归函数非常适合说明带有回溯的递归搜索的结构。它从检查退出条件开始row < n,然后继续进行递归级别的搜索决策(即,为女王编号选择一个可能的位置row)。每次迭代后,将以该函数到目前为止已发现的配置为基础进行递归调用。最终,当row到达深度较深n的递归调用时,它“自下而上” n


1
+1为“正确”的递归函数提供了有条件的,大量不正确的递归函数
吉米·霍法,2012年

6
+1永远“递归”`Turtle(){Turtle();}
Mindor先生2012年

1
@ Mind.Mindor我爱“一直到乌龟”报价:)
dasblinkenlight 2012年

那让我微笑:-)
马丁·韦尔伯格

2
“所有正确的递归函数还具有某种条件,可以防止它们永远“递归”。” 非严格评估是不正确的。
2012年

12

递归函数的一般结构如下所示:

myRecursiveFunction(inputValue)
begin
   if evaluateBaseCaseCondition(inputValue)=true then
       return baseCaseValue;
   else
       /*
       Recursive processing
       */
       recursiveResult = myRecursiveFunction(nextRecursiveValue); //nextRecursiveValue could be as simple as inputValue-1
       return recursiveResult;
   end if
end

我标记为的文本/*recursive processing*/可以是任何东西。它可能 包括一个循环,如果正在解决的问题,需要它,并且还可能包括对递归调用myRecursiveFunction


1
这具有误导性,因为它暗示只有一个递归调用,并且几乎排除了递归调用本身位于循环内的情况(例如,B树遍历)。
彼得·泰勒

@PeterTaylor:是的,我试图使其保持简单。
FrustratedWithFormsDesigner 2012年

甚至没有循环的多次调用,例如遍历普通的二叉树,由于每个节点有2个子节点,因此您将有2次调用。
Izkata 2012年

6

您当然可以在递归函数中使用循环。使函数递归的唯一原因是函数在其执行路径中的某个时刻调用了自身。但是,您应该有一些条件来防止函数无法返回的无限递归调用。


1

递归调用和循环只是实现迭代计算的两种方式/构造。

一个while循环对应于一个尾递归调用(例如参见这里),也就是在你不需要保存两次迭代之间的中间结果迭代(一个周期的所有结果都准备好,当你进入下一个周期)。如果您需要存储中间结果以备后用,则可以将while循环与堆栈一起使用(请参见此处),也可以使用非尾递归(即任意)递归调用。

许多语言都允许您同时使用两种机制,并且可以选择一种更适合您的机制,甚至在代码中将它们混合在一起。在诸如C,C ++,Java等命令性语言中,通常在不需要堆栈时使用whileor for循环,而在需要堆栈时使用递归调用(隐式使用运行时堆栈)。Haskell(一种功能语言)不提供迭代控制结构,因此您只能使用递归调用来执行迭代。

在您的示例中(请参阅我的评论):

// queens should have type int [] , not int.
private boolean placeQueen(int row, int [] queens, int n)
{
    boolean result = false;
    if (row < n)
    {
        // Iterate with queens[row] = 1 to n - 1.
        // After each iteration, you either have a result
        // in queens, or you have to try the next column for
        // the current row: no intermediate result.
        while ((queens[row] < n - 1) && !result)
        {
            queens[row]++;
            if (verify(row,queens,n))
            {
                // I think you have 'result' here, not 'ok'.
                // This is another loop (iterate on row).
                // The loop is implemented as a recursive call
                // and the previous values of row are stored on
                // the stack so that we can resume with the previous
                // value if the current attempt finds no solution.
                result = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

1

您以为递归与迭代或循环之间存在关系是正确的。递归算法通常使用尾调用优化手动或什至自动转换为迭代解决方案。

在八个皇后区中,递归部分与存储回溯所需的数据有关。当您想到递归时,考虑什么压入堆栈是很有价值的。堆栈可以包含在算法中起关键作用的按值传递参数和局部变量,或者有时包含与返回地址无关的东西,例如返回地址,在这种情况下,还可以是已传递的值,该值使用了皇后数但不会被算法更改。

在八个皇后区中发生的动作是,本质上,我们为前几列中的某些皇后区提供了部分解决方案,从中我们迭代确定当前列中的有效到目前为止的选择,然后递归传递以对其进行评估。剩余的列。在本地,八个皇后区跟踪正在尝试的行,如果发生了回溯,它准备逐步浏览剩余的行,或者准备返回,如果发现没有其他行可以通过简单地返回来进行进一步回溯。


0

“创建问题的较小版本”部分可以具有循环。只要该方法调用自身将问题的较小版本作为参数传递,则该方法是递归的。当然,当解决了问题的最小可能版本并且该方法返回值时,必须提供退出条件,以避免堆栈溢出条件。

您问题中的方法是递归的。


0

递归基本上可以再次调用您的函数,递归的主要优点是可以节省内存。递归中可以包含循环,它们可用于执行其他一些操作。


未敢苟同。许多算法可以是递归的或迭代的,如果您计算必须压入堆栈的返回地址,参数和局部变量,则递归解决方案通常会占用更多的内存。一些语言检测并帮助优化尾递归或尾调用优化,但这有时是特定于语言或代码的。
DeveloperDon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.