【问题标题】:Turning a recursive function into a for loop?将递归函数变成for循环?
【发布时间】:2011-02-10 19:44:29
【问题描述】:

每个递归函数都有等效的 for 循环吗? (两者都达到相同的结果)。

我有这个递归函数:

private static boolean recur(String word, int length) {
    if(length == 1 || length == 2)
        return false;
    if(length == 0)
        return true;
    if(words[length].contains(word.substring(0, length)))
        return recur(word.substring(length), word.length() - length);
    return recur(word, length-1);
}

假设 words 是一个 Set[],其中 words[i] = 一个长度为 i 的单词的集合。

我想做的是: 用一个词启动递归(比如,“stackoverflow”,没有空格),我试图找出这个词是否可以被分割成子词(“stack”,“over”,“flow”)..子词的最小长度是 3,并且假设长度为 i 的子词在 Set words[i] 中。

我可以确认这段代码有效,但它可能有内存问题,所以我想把它变成一个循环......如果可能的话。

您需要更多信息吗??

谢谢。

【问题讨论】:

    标签: java loops recursion for-loop nested-loops


    【解决方案1】:

    尾递归总是可以展开成一个循环,你的代码非常接近尾递归,所以是的。

    private static boolean recur(String word, int length) {
        if(length == 1 || length == 2)
            return false;
        if(length == 0)
            return true;
        int nextLength;
        String nextWord;
        if(words[length].contains(word.substring(0, length))) {
          nextWord = word.substring(length);
          nextLength = word.length() - length;
        } else {
          nextWord = word;
          nextLength = length - 1;
        }
        return recur(nextWord, nextLength);
    }
    

    现在这是正确的尾递归。现在把它变成一个循环:

    private static boolean recur(String word, int length) {
        int nextLength = length;
        String nextWord = word;
        while( true ) {
            if(nextLength == 1 || nextLength == 2)
                return false;
            if(nextLength == 0)
                return true;
            if(words[nextLength].contains(nextWord.substring(0, nextLength))) {
                nextWord = nextWord.substring(nextLength);
                nextLength = nextWord.length() - nextLength;
            } else {
                nextWord = nextWord;
                nextLength = nextLength - 1;
            }
        }
    }
    

    请注意,这段代码可以进一步优化,我只是想演示将递归变成循环的“自动”方法。

    【讨论】:

    • 它不仅接近尾递归。有两个递归调用,都是最后完成的事情。尾递归并不意味着只有一个递归调用是函数的最后一行(如您的“正确”示例中所示)。
    • 嗯,自动的正是我一直在寻找的,至少现在,谢谢。 TBH,我需要它来解决编程问题,所以向我展示如何优化它将帮助我避免“超出时间限制”错误;)
    • @musiKk 我们在 uni 学到了更严格的尾递归定义,但我检查了它,你是对的。转换步骤还是一样的。
    • @Mazyod Jaleel 请参阅@Dave Hinton 的回答。与其说是优化不如消除视觉混乱,但仍然如此。
    • @biziclop:你能帮我解决这个递归吗?stackoverflow.com/questions/13229722/…。我认为它类似于这个问题。我不确定您是否可以将其称为尾递归。递归在每个 if 语句的末尾。
    【解决方案2】:

    对于一般问题:是的,每个递归都可以在循环中进行转换。您可以显式地对递归创建和使用的堆栈进行建模,并在循环中对其进行修改。

    针对具体问题:

    将您的代码视为一个函数并忽略副作用,我认为您可以返回 false。

    如果您确实需要拆分词,请尝试以下操作:

    have a list with the word to split.
    iterate until list after iteration is the same as before.
        iterate over list
            if the word can be split, replace it in the list with its parts
        end of loop
    end of loop
    

    【讨论】:

      【解决方案3】:

      简短的回答:通常你可以,在这种情况下你很容易,但是如果你修复代码逻辑中的错误,你必须担心自己维护堆栈。

      长答案:

      首先,递归方法的迭代版本:

      private static boolean recur(String word, int length) {
          while(true) {
              if(length == 1 || length == 2)
                  return false;
              if(length == 0)
                  return true;
              if(words[length].contains(word.substring(0, length))) {
                  int newlen = word.length() - length;
                  word = word.substring(length);
                  length = newlen;
              }
              else {
                  --length;
              }
          }
      }
      

      这通过将递归调用替换为对参数的赋值,并将其全部粘贴在一个循环中来实现。

      但就像您的原始代码一样,这包含一个错误!

      (我想不出这个 bug 使用的实际单词,所以想象一下我虚构的单词是真实的单词。)

      假设你的长词是 ABCDEFGH,ABCD、EFGH 和 ABCDE 都是词,但 FGH 不是。 recur先找最长的词,所以会匹配ABCDE,然后发现FGH不是词,告诉你ABCDEFGH不能切成子词。

      但它可以分为 ABCD 和 EFGH!它错过了较短的初始匹配,因为一旦找到匹配项,您就不会考虑其他选项。 (如果您的代码从查找较短的匹配项开始,如果 ABC 是一个单词而 DEFGH 不是,它仍然无法工作。)

      所以:

      private static boolean recur(String word, int length) {
          if(length == 1 || length == 2)
              return false;
          if(length == 0)
              return true;
          return (words[length].contains(word.substring(0, length)))
                  && recur(word.substring(length), word.length() - length))
                 || recur(word, length-1);
      }
      

      现在,将其转换为迭代方法更加困难:有时您有 两个 递归调用。这意味着简单地分配给您的参数是行不通的,因为您需要参数的原始值来计算第二次调用的参数。您必须显式管理具有所有不同值的堆栈或队列,因为现在语言不适合您。

      【讨论】:

      • 一般来说,你可以。您所要做的就是将递归和非递归之间的划分视为隐式或显式堆栈之间的划分。
      • 由于某种原因我错过了那个特殊情况,而你省去了我寻找它的痛苦!但仍然,这不是问题的答案..所以谢谢,但我不能选择它作为最佳答案..
      • @Martinho 非常正确 --- 我的意思是指出,当原始函数不是尾递归时,这并不容易,而且我匆忙写错了:-(
      • 是的,很容易看出为什么它是可能的(你只需要考虑堆栈),但并不那么容易看到如何 i> 这是可能的,除非它是尾递归。
      【解决方案4】:

      我回答你的第一个问题:是的,每个递归函数都有一个迭代等价物。 Read more.

      这也与CPS 有关(不完全是因为流行的Java VM 并不真正支持尾调用)。转换尾递归函数(例如问题中的那个)应该特别容易。

      【讨论】:

        【解决方案5】:

        @biziclop 演示了如何将代码重构为非递归。我会更进一步并减少代码。

        • length 不再作为参数传递。
        • 使用两个循环来简化逻辑。
        • 修改本地单词值以简化。

        代码

        private static boolean recur(String word) {
            LOOP: for(;;) {
                for (int len = word.length(); len >= 3; len--)
                    if (words[len].contains(word.substring(0, len))) {
                        word = word.substring(len);
                        continue LOOP;
                    }
                return word.length() == 0;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2012-07-12
          • 1970-01-01
          • 2013-05-31
          • 1970-01-01
          • 2019-05-30
          • 1970-01-01
          • 2011-08-18
          • 2015-07-14
          相关资源
          最近更新 更多