【问题标题】:recursive function stack overflow递归函数栈溢出
【发布时间】:2012-07-26 00:56:31
【问题描述】:

这个函数接受一个包含'*'和'?'的字符串wild通配符,并用 nodeT *w 的树数据库中的可能字符替换通配符。 out 保存一个临时字符串。每个候选者都被添加到一个引用的 bst 中。

void Lexicon::matchRegExpHelper(nodeT *w, string wild, Set<string> &matchSet, string out)
{   
    if (wild == "") matchSet.add(out);

    else {
        if (wild[0] != '*' || wild[0] != '?') { //this parses up to the wildcard, earlier versions used a position parameter and looped through the prefix chars recursively
            for (int j = 0; j < w->alpha.size(); j++)
                if (wild[0] == w->alpha[j].letter) matchRegExpHelper(w->alpha[j].next, wild.substr(1), matchSet, out+=wild[0]);
        } else {
            for (int i = 0; i < w->alpha.size(); i++) { 
                if (wild[0] == '?') matchRegExpHelper(w->alpha[i].next, wild.substr(1), matchSet, out+=w->alpha[i].letter);//follow path
                else { //logically, wild[0] == '*' must be true
                    if (ontLength == (wild.length() + out.length())) matchRegExpHelper(w->alpha[i].next, wild.substr(1), matchSet, out+=w->alpha[i].letter); //ontology is full, treat like a '?'
                    else matchRegExpHelper(w->alpha[i].next, wild.substr(1), matchSet, out+=(w->alpha[i].letter+'*')); //keep adding chars
                }
            }
        }
    }
}

当到达第一个通配符时,函数重新开始 - 我尝试使用 for 循环、不使用循环和不同的“修剪”方法重写它。我缺少一些基本的东西,并怀疑这是一个回溯问题。最终堆栈溢出。

问题:1)我在概念上缺少什么,以及 2)如何修复此功能?

没有for循环的版本 - 测试用例有点不同但相似,我必须测试它才能再次找到它

else {
            if (wild[0] == '?'){
                matchRegExpHelper(w, wild, ++pos, matchSet, out);//return and check next path
                matchRegExpHelper(w->alpha[pos].next, wild.substr(1), 0, matchSet, out+=w->alpha[pos].letter);//follow path
            }
            if (wild[0] == '*'){
                matchRegExpHelper(w, wild, ++pos, matchSet, out);//return and check next path
                if (ontLength == (wild.length() + out.length()))matchRegExpHelper(w->alpha[pos].next, wild.substr(1), 0, matchSet, out+=w->alpha[pos].letter); //ontology is full, treat like a '?'
                else matchRegExpHelper(w->alpha[pos].next, wild.substr(1), 0, matchSet, out+=(w->alpha[pos].letter+'*')); //keep adding chars
            }
            if (wild[0] == w->alpha[pos].letter) matchRegExpHelper(w->alpha[pos].next, wild.substr(1), 0, matchSet, out+=wild[0]);  

            matchRegExpHelper(w, wild, ++pos, matchSet, out);//check next path
        }
        for (int i = 0; i < w->alpha.size(); i++) matchRegExpHelper(w->alpha[i].next, wild.substr(1), 0, matchSet, out+=wild[0]);//step over char

最后的 for 循环试图修复溢出,我认为某些线程可能没有案例,但我希望将它们修剪,所以不知道该怎么做

【问题讨论】:

  • 你试过使用调试器吗?
  • @Basile_Starynkevitch 也许我不知道调试器是什么,但我按下了“开始调试”的绿色箭头 - 所以我认为答案是肯定的。
  • 您需要学习如何使用您的开发工具进行调试。花时间阅读一些文档不会有害。您需要设置断点并查看回溯。
  • 我将搜索“回溯”——我不是程序员,这是我的研究生实验,这或多或少是我需要重写的最后一个函数。我在整个过程中放置​​了断点并遵循了路径,但我还没有看到用于回溯的工具。
  • 每次到达通配符时,它都会从调用函数的位置重新开始。此外,我阅读的前几个网页令人困惑——我仍然不知道回溯是什么。也许调用堆栈有问题,但查看调用堆栈并没有太大帮助,它只是一遍又一遍地使用相同的函数。

标签: visual-studio-2008 recursion stack-overflow backtracking


【解决方案1】:

这个条件总是成立的: (wild[0] != '*' || wild[0] != '?')

由于任何字符都不同于两者之一,因此您的意思可能是 (wild[0] != '*' && wild[0] != '?')

我希望这可以帮助你取得一些进步......

另外从概念上讲,我通常不在递归函数中使用“for”,尝试重写它而不使用“for”可能会更清晰,也许效率不高,但一旦它起作用,你就可以开始微调算法。 .

【讨论】:

  • 很奇怪,这似乎是非此即彼的逻辑,但你是对的。是否有理由避免递归中的 for 循环,我有一个我之前写的不使用 for 循环的版本 - 它也有逻辑错误。
  • 它仍然溢出,这些很好,但它们没有解决问题 - 我认为这是一个回溯问题或未终止松散循环的函数的溢出。当我参加 CS 时,我遇到了这个问题,TA 解释了它,但那是在 09 年
  • 递归函数的想法是它们之所以起作用,是因为每次迭代(新调用)都会解决一部分问题,而下一次将其作为前置条件或后置条件,具体取决于递归的类型。跨度>
  • 在您的情况下,如果您根据字符串的大小执行了“for”,但是当您处理字符串的一个位置时,字符串的大小发生了变化,算法将失败。注意我是说“做了”,因为我不确定这是你的情况。
【解决方案2】:

每个递归都应该有一个基本的终止条件。

我有这样的感觉

 if (wild[0] != '*' || wild[0] != '?') 

是错误的(该测试总是正确的,因为一个字符不能同时是 '*''?'),它应该是一个连词 &amp;&amp; 而不是一个析取词 ||

但正如我在评论中所说,你真的应该学习如何使用debugger(即输入breakpoints,询问backtraces,查询变量的值)。 p>

除了这个特定的程序(或家庭作业)之外,熟悉调试是一项对你的一生都很有价值的技能。

【讨论】:

  • 这不是家庭作业 - 毕业,也许像邪恶博士的家庭作业;除非我是教授,否则这可能是家庭作业——实际上现在是汽车旅馆工作:)
  • @Basile_Starynkevitch,我一直在重读您的 cmets,但看不到您提出的解决方案?另外,我并不是说我只想完成这项任务并继续前进,当然生活知识很有价值,我们都会在一生中编写软件,我无意给人留下你似乎有的印象捡起来。
【解决方案3】:

这段代码存在三个问题:

@Basile_Starynkevitch 指出了使用调试器跟踪路径的重要性 - 在 VS2008 的这种情况下,堆栈允许解析步骤并通过多个函数记录返回的路径。

@Picarus 正确记录了终止条件中的错误

这些解决方案导致发现:

解决方案,这个 void 函数需要两个return;,一个在终止条件之后终止,另一个在末尾捕获修剪过的线程。从阅读几个网站,似乎在一个void函数中的return;是不寻常的,但这种递归和多路径的情况,它是必需的。

【讨论】:

    猜你喜欢
    • 2017-10-20
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多