【问题标题】:How are recursive backtracking returns handled with the void typevoid 类型如何处理递归回溯返回
【发布时间】:2012-08-02 18:11:17
【问题描述】:

为了概括这个问题,我从 Zelenski CS 课程讲义中借用了材料。而且,这与我的具体问题有关,因为几年前我从不同的讲师那里上课并学习了这种 C++ 方法。讲义是here。由于我偶尔使用它,因此我对 C++ 的理解很低。基本上,我需要编写程序的几次我回到课堂资料,找到类似的东西并从那里开始。

在这个例子中(第 4 页),Julie 在字符串函数中使用递归算法查找单词。为了减少递归调用的数量,她添加了一个决策点bool containsWord()

string FindWord(string soFar, string rest, Lexicon &lex)
{
  if (rest.empty()) {
   return (lex.containsWord(soFar)? soFar : "");
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     string found = FindWord(soFar + rest[i], remain, lex);
     if (!found.empty()) return found;
   }
  }
 return ""; // empty string indicates failure
}

为了增加该算法的使用灵活性,是否可以将其实现为 void 类型?

void FindWord(string soFar, string rest, Lexicon &lex, Set::StructT &words)
{
  if (rest.empty()) {
    if (lex.containsWord(soFar)) //this is a bool
       updateSet(soFar, words); //add soFar to referenced Set struct tree
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     return FindWord(soFar + rest[i], remain, lex, words); //<-this is where I am confused conceptually
   }
  }
 return; // indicates failure
}

还有,没有回报怎么办

void FindWord(string soFar, string rest, Lexicon &lex, Set::StructT &words)
{
  if (rest.empty()) {
    if (lex.containsWord(soFar)) 
       updateSet(soFar, words); //add soFar to Set memory tree
  } else {
   for (int i = 0; i < rest.length(); i++) {
     string remain = rest.substr(0, i) + rest.substr(i+1);
     FindWord(soFar + rest[i], remain, lex, words); //<-this is where I am confused conceptually
   }
  }
}

【问题讨论】:

    标签: c++ recursion void backtracking recursive-backtracking


    【解决方案1】:

    第一个代码片段将尝试rest 的所有排列,附加到soFar 的初始值(可能是一个空字符串?)。它将停止在lex 中找到的第一个单词。找到该单词后将立即返回,此时搜索将被缩短。如果lex 中没有任何内容,则当所有for 循环都运行到最后时,最终将返回空字符串。

    第二个片段只会尝试一个单词:初始soFarrest 字符串的连接。如果连接的字符串在lex 中,它将调用updateSet。然后它会返回,表示失败。不会执行进一步的搜索,因为来自for 循环内部的return 是无条件的。

    所以这两个功能是完全不同的。要使第二个代码的行为与第一个代码一样,您需要它返回其他内容以指示成功,并且仅在FindWord 调用返回值指示成功时从for 循环内返回。很明显,void 不能用来表示failuresuccess。至少,您需要为此返回 bool 值。

    如果没有返回,您的第三个代码将执行详尽的搜索。 rest 的初始字符串值的每个可能的排列都将被尝试,以在词典中找到。

    您可以像这样想象正在发生的事情:

    FindWord:   soFar=""     rest=...........
        for:    i=...    rest[i]=a
           call findWord
    
    FindWord:   soFar=a       rest=..........
        for:    i=...    rest[i]=b
           call findWord
    
    FindWord:   soFar=ab       rest=.........
        for:    i=...    rest[i]=c
           call findWord
           if return, the loop will be cut short
           if not, the loop continues and next i will be tried
    
     ......
    
    FindWord:   soFar=abcdefgh...      rest=z
        for:    i=0      rest[0]=z
           call findWord
    
    FindWord:   soFar=abcdefgh...z      rest=""      // base case
        // for:    i=N/A    rest[i]=N/A
        if soFar is_in lex                           // base case
          then do_some and return soFar  OR  success
          else             return ""     OR  failure
    

    每次达到基本情况(rest 为空)时,我们在堆栈上都有n+1FindWord 调用帧,用于初始n 字符串中的n 字母。

    每次到达底部时,我们都会从rest 中挑选出所有字母。执行检查以查看它是否在lex 中,然后控制返回上一级。

    因此,如果没有返回,每个for 循环将运行到其结束。如果返回是无条件的,则只会尝试一种排列 - 微不足道的排列。但如果返回是有条件的,那么整个事情只会在第一次成功时停止。

    【讨论】:

    • @Will_Ness 我添加了一个没有返回的版本 - 这接近我使用的最后一个工作版本。您能否解释或指出解释退货目的的参考资料。
    • @forest.peterson 不要这么快就接受了。 :) :) 一些/很多人会认为您的问题已经完全解决,甚至不会看您的问题。 :)
    • @Will_Ness 他们本可以在 CS 课上花一些时间解释 return 语句 - 他们认为这是直观的。我会阅读更多,考虑一下,并尝试一些不同的代码版本,并在这里等待更多的 cmets。在void 递归return 中,放置似乎是调整算法的重要工具。
    • 它有帮助,我会在剩下的时间里反复试验并学习,以便更好地理解
    • 关于return:它从当前工作的函数返回给它的调用者。据说在这种情况下,“控制”会传回给函数的调用者。在这里,它又回到了 for 循环中。如果此时该函数也决定返回,它会缩短循环。但如果不是,则循环继续 - 尝试下一个 i - 从 rest 中选择下一个字符 - 并使用新的 soFarrest 参数对 FindWord 函数进行下一次调用,创建一个新的FindWordthe run time call stack 上的“执行框架”(或“调用框架”)。
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 2013-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多