回溯和递归之间的区别?

时间:2014-10-31 08:58:39

标签: recursion data-structures backtracking

回溯和递归有什么区别? 该计划如何运作?

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.
    }

7 个答案:

答案 0 :(得分:24)

这个问题就像问汽车和DeLorean之间的区别。

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

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

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

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

假设你遇到了坏叶子。你可以通过撤销最近的选择来回溯继续搜索好叶子,然后在该组选项中尝试下一个选项。如果您的选项用完了,请撤消此处的选择,并在该节点尝试其他选择。如果你最终没有留下任何选项,那么就找不到好叶子了。“

这需要一个例子:

enter image description here

您的代码只是递归,因为如果结果不符合您的目标,您永远不会回来。

答案 1 :(得分:11)

递归描述了调用你所在的相同函数。递归函数的典型例子是阶乘,即类似

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

你在这里看到的是这个事实本身。这就是所谓的递归。你总是需要一个让递归停止的条件。这里if(n==1)结合以下事实:每次调用n时,n总是会减少(fact(n-1)

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

您描述的程序使用递归。与阶乘函数类似,它将参数减1,如果n <1则结束(因为那时它将打印ar而不是其余部分。)

答案 2 :(得分:1)

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

答案 3 :(得分:1)

递归-

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

回溯-

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

答案 4 :(得分:0)

递归-不放弃任何值;

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

还请注意,回溯会在函数体中多次调用自身,而对于递归而言通常是不正确的

答案 5 :(得分:0)

递归就像一个自下而上的过程。您可以仅通过使用子问题的结果来解决问题。

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

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

答案 6 :(得分:0)

递归就像您显示的一样。如果例程A调用A,或者如果A调用B,而B调用A,则为递归。

回溯不是一种算法,它是一种控制结构。使用回溯时,会出现一个程序 以便能够向后运行。在编写程序来玩象棋这样的游戏时,这很有用,您需要在其中提前一些动作。当程序想要移动时,它选择一个动作,然后切换到镜像对手程序,该程序选择一个动作,依此类推。如果初始程序到达“好地方”,它会说“是”并执行其第一步。如果任何一个程序到达“坏处”,它都希望退出并尝试另一步。

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

好的,因此,如果这个想法具有吸引力,您将如何做? 首先,您需要一种表示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倍,除非性能确实是一个问题,否则它是可以忍受的。 (请记住,真正的回溯可能涉及相当多的机械,制造lambda等)。

这是我使用的基本代码:

#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例程构建了一个本地定理证明器。