【问题标题】:Difference between backtracking and recursion?回溯和递归的区别?
【发布时间】:2014-12-27 13:01:09
【问题描述】:

回溯和递归有什么区别?这个程序是如何工作的?

void generate_all(int n)
{
    if(n<1) printf("%s\n", ar);
    else{
            ar[n-1]='0';        //fix (n)th bit as '0'
            generate_all(n-1);  //generate all combinations for other n-1 positions.
            ar[n-1]='1';        //fix (n)th bit as '1'
            generate_all(n-1);  //generate all combinations for other n-1 positions.
    }
}

【问题讨论】:

  • 我想你最好把你的问题说清楚一点。 ar 甚至没有在您提供的代码中定义。
  • 好问题!正如您所展示的那样,递归用作完整枚举所有可能结果的实现机制;而不是仅仅在基本情况下打印,添加一个测试,一个测试通过时的条件打印,以及可选的纾困,你已经得到了一个针对特定问题的迷你 Prolog。

标签: recursion data-structures backtracking


【解决方案1】:

这个问题就像问汽车和DeLorean 有什么区别。

在递归函数中调用自身直到达到一个基本情况。

在回溯中,您使用递归来探索所有可能性,直到获得问题的最佳结果。

可能有点难以理解,我附上here的一些文字:

“从概念上讲,你从一棵树的根部开始;这棵树可能有一些好叶子和一些坏叶子,尽管叶子可能都是好的或坏的。你想得到一片好叶子。在每个节点上,从根开始,您选择要移动到的其中一个子节点,并一直保持下去,直到到达叶子节点。

假设你得到一片坏叶子。您可以通过撤消最近的选择并尝试该选项集中的下一个选项来回溯以继续搜索好叶子。如果您用完了选项,请撤消让您来到这里的选择,并在该节点尝试另一个选择。如果你最终在根部没有任何选择,就找不到好的叶子。”

这需要一个例子:

您的代码只是递归,因为如果结果不符合您的目标,您将永远无法返回。

【讨论】:

    【解决方案2】:

    递归描述了对您所在的同一函数的调用。递归函数的典型示例是阶乘,即类似

    int fact(int n) {
        int result;
        if(n==1) return 1;
        result = fact(n-1) * n;
        return result;
    }
    

    你在这里看到的是事实自称。这就是所谓的递归。您总是需要一个使递归停止的条件。这里是if(n==1),再加上每次调用 n 时都会减小 (fact(n-1))

    回溯是一种算法,它试图在给定参数的情况下找到解决方案。它为解决方案建立候选者并放弃那些不能满足条件的候选者。要解决的任务的典型示例是Eight Queens Puzzle。回溯也常用于神经网络中。

    您描述的程序使用递归。与阶乘函数类似,它将参数减 1 并在 nar 而不是其余的)。

    【讨论】:

      【解决方案3】:

      递归 - 没有放弃任何值;

      回溯 - 放弃一些候选解决方案;

      还要注意,回溯会在函数体中多次调用自身,而递归通常不会这样

      【讨论】:

        【解决方案4】:

        在我的理解中,回溯是一种算法,就像所有其他算法一样,如 BFS 和 DFS,但递归和迭代都是方法,它们比算法处于更高级别,例如,要实现 DFS,你可以使用递归,这很直观,但你也可以使用带有堆栈的迭代,或者你也可以认为递归和迭代只是支持你的算法的方法。

        【讨论】:

          【解决方案5】:

          递归就像你展示的那样。如果例程 A 调用 A,或者如果 A 调用 B 而 B 调用 A,这就是递归。

          Backtrack 不是算法,它是一个控制结构。当使用回溯时,一个程序似乎能够向后运行。这在编写程序来玩像国际象棋这样的游戏时很有用,您想在其中预测一些移动。当程序想要移动时,它会选择一个移动,然后切换到它的镜像对手程序,它会选择一个移动,依此类推。如果初始程序到达“好地方”,它会说“Yay”并执行它所做的第一步。如果任何一个程序到达“糟糕的地方”,它都想退出并尝试另一个动作。

          您可以将其视为搜索一棵树,但如果移动选择很复杂,则将其视为程序“备份”到它选择移动的最后一个位置可能更直观,选择不同的移动,然后再次向前奔跑。

          好的,如果这个想法很有吸引力,你会怎么做? 首先,您需要一种表示choice 的语句,其中选择了移动,您可以“备份”并修改您的选择。 其次,每当你有一个像A;B 这样的语句序列时,你就将A 设为一个函数,并将一个能够执行B 的函数传递给它。像A(lambda()B(...)) 这样的东西。因此,当A 完成执行时,在返回之前它会调用执行B 的参数。 如果A 想要“失败”并开始“向后运行”,它只会返回而不调用调用B 的函数。 我知道这很难理解。我已经通过 LISP 中的宏完成了它,并且效果很好。用像 C++ 这样的普通编译器语言来做这件事非常麻烦。

          我在 C/C++ 中以一种保留“美感”但实际上并没有倒退的方式做了类似的事情。 这个想法是你这样做是为了执行某种深度优先树搜索。 但是您可以改为从树的根部向下执行一系列“刺”,每次“刺”时遵循不同的路径。 出于性能原因,这可能会遭到反对,但实际上并没有花费那么多,因为大部分工作都发生在树的叶子上。如果树有 3 层深并且每个节点的分支因子为 5,这意味着它有 5 + 25 + 125 或 155 个节点。但在从根开始的一系列“刺”中,它访问了 125 * 3 = 375 个节点,性能损失不到 3 倍,这可以相当容忍,除非性能真的有问题。 (请记住,真正的回溯可能涉及相当多的机器,制作 lambdas 等等。)

          这是我使用的基本代码:

          #define NLEVEL 20
          int ia[NLEVEL];
          int na[NLEVEL];
          int iLevel = 0;
          int choose(int n){
              if (ilevel >= ns){ na[ns]=n; ia[ns]=0; ns++; }
              return ia[ilevel++];
          }
          void step(){
              while (ns > 0){
                  if (++ia[ns-1] >= na[ns-1]) ns--;
                  else break;
              }
          }
          bool search(int iLevel){
              iLevel++;
              switch(choose(2)){
              break; case 0:;
                  // see if 0 is a win. if not, fail by returning false
              break; case 1:
                  // choose move 1 and recur
                  return search(iLevel);
              }
              return true;
          }
          // this is the "top level" routine
          void running(){
              ns = 0;
              // repeat for stabs into choice tree until success
              do {
                  bool bSuccess = search(0);
                  if (bSuccess){
                      // Yay!
                      break;
                  }
                  step(); // this advances the stack of choices
              } while(ns > 0); // stop when success or there are no more choices
          }
          

          我已经使用此代码构建了一个自制的定理证明器来代替 search 例程。

          【讨论】:

            【解决方案6】:

            递归就像一个自下而上的过程。您只需使用子问题的结果即可解决问题。

            例如,使用递归的反向 LinkedList 只是在已经反向的子列表上附加一个头节点。https://leetcode.com/problems/reverse-linked-list/discuss/386764/Java-recursive

            回溯仍然是一个自上而下的过程。有时你不能仅仅使用子问题的结果来解决问题,你需要将你已经得到的信息传递给子问题。此问题的答案将在最低级别计算,然后这些答案将与您在此过程中获得的信息一起传递回问题。

            【讨论】:

              【解决方案7】:

              递归 -

              • 问题可以分解为相同类型的较小问题。
              • 需要基本情况​​

              回溯 -

              • 采用所有可能的组合来解决计算问题的通用算法技术。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-12-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-04-03
                • 1970-01-01
                • 1970-01-01
                • 2017-03-05
                相关资源
                最近更新 更多