我被赋予了编写字符串置换程序的任务。我理解逻辑,但不了解此程序中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("%s\n", 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;
}
答案 0 :(得分:0)
“回溯”意味着,您在解决方案空间中向后退一步(将其视为决策树,并且您将升级到一个级别)。如果你可以排除决策空间中的某些子树,通常会使用它,并且与完全探索决策树相比,显着提升性能当且仅当你很可能排除更大的部分时解决方案空间。
您可以在此处找到类似算法的详尽说明:Using recursion and backtracking to generate all possible combinations
答案 1 :(得分: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()来打印每一个对这个新字符串进行排列,之后我们将字符串恢复到之前的状态,这样我们就可以使用原始字符串在下一个位置重复相同的过程。
就个人而言,我不喜欢那句“回溯”的评论。一个更好的术语是“回头”,因为在那一点上递归回来并为下一次递归调用准备字符串。 Backtrack通常用于您探索子树并且找不到解决方案的情况,因此您返回(回溯)并尝试不同的分支。取自维基百科:
回溯是查找所有(或部分)的通用算法 一些计算问题的解决方案,逐步建立 候选人的解决方案,并放弃每个部分候选人c (“回溯”)一旦确定c不可能 完成了有效的解决方案。
请注意,此算法不会生成排列集,因为当重复字母时,它可以多次打印相同的字符串。一个极端的情况是当你将它应用于字符串“aaaaa”,或任何其他带有一个唯一字母的字符串时。