【问题标题】:Stopping Backtracking停止回溯
【发布时间】:2010-04-25 14:22:42
【问题描述】:

在 C/C++ 中有什么方法可以在找到第一个解决方案后停止回溯算法而不退出程序。

我希望我的函数立即退出函数,而不是逐级退出递归并声明返回。

【问题讨论】:

  • C 和 C++ 是非常不同的语言。你用的是哪个?
  • 我正在使用 C++,但我对 C 和 C++ 都感兴趣
  • 通常回溯算法处理一些树的高度,并且递归不是很。微优化可能不会让您的程序更高效。
  • 您可以使用“期望模式”,即抛出一个在递归函数之外捕获的无意义异常。

标签: c++ c algorithm backtracking


【解决方案1】:

一种快速而肮脏的方法是抛出一个异常并在基本级别捕获它(现在很多人会尖叫只使用异常处理错误,我认为,找到一个解决方案是一个特殊的事件,因为没有找到一是常态)

【讨论】:

    【解决方案2】:

    如果您在完成后设置了一个标志,然后在您的函数中检查了该标志,您可以解决这个问题。例如

    void foo(..)
    {
       if (g_done) return;
    ...
    }
    

    【讨论】:

      【解决方案3】:

      在不确切知道您需要什么的情况下,我会考虑实现您自己的堆栈,并完全避免递归。这样退出回溯树(单个“返回”)变得微不足道,并且还可以通过再次调用函数来恢复搜索以找到下一个解决方案,假设用户定义的堆栈的状态被保留(在静态变量中)。当然,将简单的递归程序转换为循环会产生一些编程开销,但它非常简单。

      【讨论】:

      • 管理自己的堆栈很容易出错,除非你需要榨取最后一点性能,否则将自然递归算法混淆为迭代算法几乎没有什么好处。
      • 假设不需要重用堆栈,我认为分配/解除分配堆栈不会比递归和返回更有效。
      • 使用您自己的堆栈,您可以预先分配到最大深度,避免遇到堆栈溢出,避免存储返回地址,避免存储不需要在递归调用中保留的局部变量,避免方法序言 /尾声,使优化器更容易,可以存储快照并在以后继续,并且 - 如上所述 - 可以在任何时间,任何深度停止。 ---- 对于不了解递归的读者来说,通常代码更多但更容易分析,可以更好地调试+诊断,并且这是一种有时需要的常见代码转换。
      【解决方案4】:

      这实际上取决于您的实现,但您可以设置一些特殊参数并将其用作标志,例如“我们得到解决方案了吗?如果是,则中止您当前的例程并仅获得输出的解决方案”。

      【讨论】:

      • 你如何中止例程,如果你声明'return'它只会退出当前的递归级别
      • 如果标志设置为退出标志,添加类似if (flag == exit_flag) return 的内容将递归退出每个回收级别。只需找到一种方法来实现您的算法。
      • 如果我错了,请见谅,因为我从未广泛使用过 C/C++,但没有从函数调用堆栈返回也涉及堆栈展开?
      • 您是否真的测量过性能并知道这是一个问题?最佳实践是避免优化,直到配置文件指出存在问题。如果确实存在问题,则应考虑另一种方法(例如有限状态机)
      • 我很确定你永远不会到达that big depth,你会感觉到不同。打开优化通常会折叠那些return - return - return ... 块,一切都会好起来的。如果您不信任我,您可以简单地分析标志方法和异常/ longjmp 方法。
      【解决方案5】:

      为什么要让函数立即退出?不回溯堆栈是危险的,因为上面可能有需要销毁的对象。抛出异常可能是由于你的技巧,它会清理堆栈。提供有关您正在尝试做的事情的更多信息,我们或许可以提供其他方法。

      【讨论】:

        【解决方案6】:

        您可以在 C 和 C++ 中使用 setjmp()/longjmp() 来获得一种粗略但有效的方法来绕过一直传递标志的需要。

        【讨论】:

        • 我认为 setjmplongjmp 不应该在 C++ 中使用
        • 谨慎使用非常,longjmp绕过析构函数调用。虽然速度比不上。
        • 它们可能永远不应该被使用(因此粗略但有效的评论),但它们在那里并且可以在 C 和 C++ 中使用(小心!)。
        【解决方案7】:

        如果您的回溯算法实际上递归的深度足以影响这一点,那么您不应该使用递归,因为您有爆栈的危险。与其犯一些涉及 longjmp 的暴行,不如考虑将算法重写为迭代方法,使用自己的堆分配堆栈存储代表状态的 POD 对象。当您找到解决方案时,可以通过一个有效的步骤销毁状态容器并返回答案。

        Way to go from recursion to iteration

        【讨论】:

          【解决方案8】:

          首先,请注意,您不能对任何递归函数执行此操作。 考虑以下代码:

          int SumNum(int nMax)
          {
              if (0 == nMax)
                  return 0;
              else
                  return nMax + SumNum(nMax-1);
          }
          

          在回溯过程中计算实际值。

          但是,您可以通过以下方式重写代码:

          int SumNum(int nMax, int nSum)
          {
              if (0 == nMax)
                  return nSum;
              else
                  return SumNum(nMax-1, nSum+nMax);
          }
          

          现在,您可以执行以下技巧:

              int SumNum(int nMax, int nSum)
              {
                  if (0 == nMax)
                      throw nSum;
                  else
                      return SumNum(nMax-1, nSum+nMax);
              }
          
          
          f()
          {
                  int nSum;
          
                  try
                  {
                      SumNum(100, 0);
                  }
                  catch (int _nSum)
                  {
                      nSum= _nSum;
                  }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-08-23
            • 1970-01-01
            • 2022-09-23
            • 2022-12-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多