任何编程语言的回溯

Backtrack in any programming language

本文关键字:回溯 编程语言 任何      更新时间:2023-10-16

我的任务是编写一个字符串排列程序。 我理解逻辑,但不了解该程序中Backtrack的确切含义。 请解释 for 循环功能、何时调用swap、何时调用permutate()以及回溯的确切含义。

# include <stdio.h>

void swap (char *x, char *y)
{
    char temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

void permute(char *a, int i, int n) 
{
   int j; 
   if (i == n)
     printf("%sn", a);
   else
   {
        for (j = i; j <= n; j++)
       {
          swap((a+i), (a+j));
          permute(a, i+1, n);
          swap((a+i), (a+j)); //backtrack
       }
   }
} 

int main()
{
   char a[] = "ABC";  
   permute(a, 0, 2);
   getchar();
   return 0;
}

绘制调用堆栈的草图可以帮助您了解算法的工作原理。示例字符串"ABC"是一个很好的起点。基本上,这就是ABC将要发生的事情:

permute(ABC, 0, 2)
    i = 0
    j = 0
    permute(ABC, 1, 2)
        i = 1
        j = 1
        permute(ABC, 2, 2)
            print "ABC"
        j = 2
        string = ACB
        permute(ACB, 2, 2)
            print "ACB"
        string = ABC
    j = 1
    string = BAC
    permute(BAC, 1, 2)
        .... (everything starts over)

像往常一样,在上面的示例中,缩进定义了每个递归调用内部发生的情况。

for 循环背后的原因是字符串 ABC 的每个排列都由 ABC、BAC 和 CBA 给出,加上子字符串 BC、AC 和 BA 的每个排列(从前面的每个字母中删除第一个字母(。对于任何字符串 S,通过将每个位置与第一个位置交换以及每个字符串的所有排列来获得可能的排列。可以这样想:任何排列的字符串都必须以原始字符串中的一个字母开头,因此您将每个可能的字母放在第一个位置,并递归地将相同的方法应用于字符串的其余部分(没有第一个字母(。

这就是循环正在做的事情:我们扫描字符串从当前起点(即 i(到终点,并在每一步中将该位置与起点交换,递归调用 permute(( 以打印这个新字符串的每个排列,然后我们将字符串恢复到以前的状态, 这样我们就有了原始字符串来对下一个位置重复相同的过程。

就个人而言,我不喜欢"回溯"的评论。一个更好的术语是"绕回",因为此时递归会卷回,你为下一个递归调用准备字符串。回溯通常用于您探索子树但没有找到解决方案的情况,因此您返回(回溯(并尝试不同的分支。摘自维基百科:

回溯是用于查找全部(或部分(的通用算法 一些计算问题的解决方案,以增量方式构建 解决方案的候选者,并放弃每个部分候选 c ("回溯"(一旦它确定 c 不可能 完成到有效的解决方案。

请注意,此算法不会生成排列集,因为当有重复的字母时,它可以多次打印相同的字符串。一个极端的情况是,当您将其应用于字符串"aaaaa"或任何其他具有一个唯一字母的字符串时。

">

回溯"意味着,你在解决方案空间中后退一步(把它想象成一个决策树,你正在上升一个级别(。它通常用于可以排除决策空间中的某些子树的情况,并且与完全探索决策树相比,当且仅当很可能排除解决方案空间的较大部分时,它才能显着提高性能。

您可以在此处找到类似算法的详尽扩展: 使用递归和回溯生成所有可能的组合