Java 有没有一次你不使用递归?

Java 有没有一次你不使用递归?,java,recursion,Java,Recursion,我有一个大学实验室的问题 编写一个简短的程序,输出所有可能的字符串,这些字符串只使用一次字符“c”、“a”、“r”、“b”、“o”和“n”。 这似乎是一个常见的面试问题,而且有很好的记录 所以我用Java编写了它,使用了一种不太难的递归方法,什么时候或者为什么选择不使用递归,最简单的方法是什么? 我开始编写一个计数器,以6为基数倒计时,然后输出将引用char并打印字符串 谢谢,是的,很多时候我不会使用递归。递归不是免费的,它在堆栈空间中有成本,而且这通常是比其他资源更有限的资源。在设置和拆除堆栈

我有一个大学实验室的问题

编写一个简短的程序,输出所有可能的字符串,这些字符串只使用一次字符“c”、“a”、“r”、“b”、“o”和“n”。

这似乎是一个常见的面试问题,而且有很好的记录

所以我用Java编写了它,使用了一种不太难的递归方法,什么时候或者为什么选择不使用递归,最简单的方法是什么?

我开始编写一个计数器,以6为基数倒计时,然后输出将引用char并打印字符串


谢谢,

是的,很多时候我不会使用递归。递归不是免费的,它在堆栈空间中有成本,而且这通常是比其他资源更有限的资源。在设置和拆除堆栈帧时,也需要花费时间,尽管时间很小

举个例子,我很可能会选择一种迭代方法,在这种方法中,很多人吹嘘的阶乘函数数量很大。计算10000!with(这看起来像Python,但这只是因为Python是一种非常好的伪代码语言):

将使用10000个堆栈帧(当然,假设编译器没有将其优化为迭代解决方案),相当多。迭代解决方案:

def factorial (n):
    r = 1
    while n > 1:
        r = r * n
        n = n - 1
    return r
将只使用一个堆栈框架,其他很少使用

的确,递归解决方案通常是更优雅的代码,但您必须根据环境的限制来调整这一点

您的
carbon
示例是我实际使用递归的示例,因为:

  • 它最多使用六个堆栈帧(字符串中每个字符一个);及
  • 它相对优雅,至少比六个嵌套循环和巨大的相等性检查要多得多
例如,以下Python代码实现了这一点:

def recur (str, pref = ""):
    # Terminating condition.

    if str == "":
        print pref
        return

    # Rotate string so all letters get a chance to be first.

    for i in range (len (str)):
        recur (str[1:], pref + str[:1])
        str = str[1:] + str[:1]

recur ("abc")
制作:

abc
acb
bca
bac
cab
cba

当然,如果您的字符串可以是10K长,我会重新考虑,因为这将涉及更多的堆栈级别,但是,如果您保持足够低的级别,递归是一个可行的解决方案。

只需使用循环,就可以避免使用递归。通常避免递归,因为它会降低代码的可读性,并且更难维护和调试。如果您的资源不足,正如paxdiablo所说,堆栈空间可能对您很有价值,那么您也应该避免使用它。

当有大量递归调用时,您的堆栈可能会爆炸,留下StackOverflower错误。一个很好的例子是用basic计算斐波那契数(或河内塔问题) 递归您将无法计算这些数字中的许多。您可以使用非定期版本。基本上,递归创建了好看的解决方案,这是一个优点。通过使用尾部递归,您可能仍然有很好的递归解决方案,但递归是有用的程序员工具。我认为理解递归对于程序员来说是“必须”的

你有解决问题的聪明方法。它可以递归求解(伪代码):

private void排列递归(字符串前缀,字符串s)
{
int N=s.长度;
如果(N>0)
对于(int i=0;i
正如上面的人所写,递归并不总是最佳解决方案(函数调用可能很昂贵,除非编译器能够优化尾部递归,否则它们会消耗堆栈)。然而,它特别适合像你这样的问题

虽然从理论上讲,可以用迭代来表示每个递归算法(例如,通过使用数组手动模拟调用堆栈),但有时等效的迭代解决方案并不那么优雅。以下是一个示例:

text = 'carbon'
n = len(text)
for permutation_i in range(factorial(n)):
    free_letters = list(text)
    divisor = 1
    for character_i in range(n, 0, -1):
        letter = free_letters.pop(permutation_i//divisor%character_i)
        print(letter, end='')
        divisor *= character_i
    print()

文本=‘碳’
n=len(文本)
对于范围内的置换_i(阶乘(n)):
自由字母=列表(文本)
除数=1
对于范围(n,0,-1)中的字符_i:
字母=自由字母.pop(排列字母//除数%character字母)
打印(字母,结尾=“”)
除数*=字符
打印()

当您的数据本质上是分层/嵌套的时,请使用递归。当数据为线性/平面时,使用迭代

在您的例子中,您可以对组合施加一种自然顺序,因此您可以将数据视为线性,但如果您将其视为一棵树,则最终将使用递归方法


如果算法的结构反映了底层问题的结构,那么最终会得到更简单、更容易理解的代码。不要仅仅因为你的CS201教授认为是递归就使用递归!酷

尾部递归与无递归版本一样有效。在这种特殊情况下,递归是最合适的解决方案。@Nicolas,只有当它被实际优化时才是正确的。尾端递归可能很复杂,但有时决定何时递归更复杂。永远,永远,永远不要使用递归。;-)仅供参考:不要假设尾部递归在JVM中得到了优化:要确定是否适合使用递归,您必须问自己,是否适合使用递归?“通常避免使用递归,因为它会降低代码的可读性,并且更难维护和调试”-这似乎是一个相当粗糙的概括。有了一定的数学背景,递归解可以非常可读。迭代方法有时过于以机器为中心,有时读起来可能有点乏味。-1我不同意答案的前半部分,特别是当这样一个大胆的声明(避免递归)没有某种引用的支持时。确实,有时使用递归,有时不使用,我们需要根据每个案件的是非曲直来看待每个案件。通常,迭代解决方案需要更多的代码来实现
private void PermutationsRecursive(string prefix, string s)
{
    int N = s.Length;

    if (N > 0)
        for (int i = 0; i < N; i++)
            PermutationsRecursive(prefix + s[i], s.Substring(0, i) + s.Substring(i + 1, N - i - 1));
    else
        WriteLine(prefix);
}

PermutationsRecursive("", "carbon");

text = 'carbon'
n = len(text)
for permutation_i in range(factorial(n)):
    free_letters = list(text)
    divisor = 1
    for character_i in range(n, 0, -1):
        letter = free_letters.pop(permutation_i//divisor%character_i)
        print(letter, end='')
        divisor *= character_i
    print()