回溯和递归有什么区别? 该计划如何运作?
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.
}
答案 0 :(得分:24)
这个问题就像问汽车和DeLorean之间的区别。
在递归函数中调用自身直到达到基本情况。
在回溯中,您使用递归来探索所有可能性,直到您获得问题的最佳结果。
可能有点难以理解,我附上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
例程构建了一个本地定理证明器。