回溯法
回溯是一种算法,用于捕获给定计算问题的部分或全部解决方案,特别是约束满足问题。该算法只能用于能够接受“部分候选解”概念的问题,并允许快速测试候选解是否可以是一个完整的解。回溯被认为是解决约束满足问题和难题的一种重要技术。它也被认为是一种很好的解析技术,也是许多逻辑编程语言的基础。
先决条件 : 递归 复杂性分析
递归回溯法是解决问题的算法技术通过渐进式地试图建立一个解决方案,一次一片,去除那些解决方案无法满足的约束问题在任何时间(时间,是指时间直到达到搜索树的任何级别)。
维基百科:回溯可以定义为一种通用的算法技术,它考虑搜索每一个可能的组合来解决一个计算问题。
回溯有三种类型的问题
1.决策问题——在这个问题中,我们寻找一个可行的解决方案。
2.优化问题——在这个问题中,我们寻找最佳解。
3.枚举问题——在这个问题中,我们找到了所有可行的解。
如何确定一个问题是否可以通过回溯来解决?
实际上,每一个约束满足问题对于任何客观解都有明确定义的约束,只要它确定了候选项不可能完成为有效解,就可以通过回溯来解决。然而,所讨论的大多数问题都可以使用其他已知的算法来解决,如动态规划算法或贪心算法,按输入大小的顺序计算对数、线性、线性-对数时间复杂度,因此在各个方面都优于回溯算法(因为回溯算法通常在时间和空间上都是指数型的)。然而,仍然存在一些问题,到目前为止只有回溯算法可以解决这些问题。
假设你面前有三个盒子,其中只有一个盒子里有一枚金币,但你不知道是哪一个。为了得到硬币,你必须一个接一个地打开所有的盒子。你将首先检查第一个盒子,如果它不包含硬币,你将不得不关闭它,检查第二个盒子,直到你找到硬币。这就是回溯,就是一个一个地解决所有的子问题,以达到最好的可能的解决方案。
考虑下面的例子来更正式地理解回溯方法,给定任意计算问题P的一个实例和与该实例对应的数据D,为了解决该问题需要满足的所有约束条件都用c表示。回溯算法的工作原理如下:
算法开始建立一个解决方案,从一个空的解集:S.s = {}
向左移动的第一步(所有可能的移动都逐个加到S上)。
这将在算法的搜索树中创建一个新的子树s。
检查 S+ s 是否满足C中的每个约束条件。
是,则子树s“符合”添加更多“子树”的条件。否则,整个子树s是无用的,所以使用参数S递归到步骤1。
如果新形成的子树s具有“资格”,则使用参数 S+s递归到步骤1。
检查S+ S返回它是整个数据D的一个解决方案。输出并终止程序。
如果没有,则返回当前s不可能有解决方案,并将其丢弃。
回溯伪代码:
void findSolutions(n, other params) :
if (found a solution) :
solutionsFound = solutionsFound + 1;
displaySolution();
if (solutionsFound >= solutionTarget) :
System.exit(0);
return
for (val = first to last) :
if (isValid(val, n)) :
applyValue(val, n);
findSolutions(n+1, other params);
removeValue(val, n);
寻找解决方案是否存在
boolean findSolutions(n, other params) :
if (found a solution) :
displaySolution();
return true;
for (val = first to last) :
if (isValid(val, n)) :
applyValue(val, n);
if (findSolutions(n+1, other params))
return true;
removeValue(val, n);
return false;
N后问题
N个皇后的问题是把N个象棋皇后放在N×N个棋盘上,这样就不会有两个皇后互相攻击。
例如,下面是4皇后问题的一个解决方案。
预期的输出是一个二进制矩阵,其中皇后放置所在的位置数据为1。例如,下面是上述4皇后解的输出矩阵。
{ 0, 1, 0, 0}
{ 0, 0, 0, 1}
{ 1, 0, 0, 0}
{ 0, 0, 1, 0}
回溯算法:
从最左边的一列开始,将皇后区逐个放在不同的列中。当我们在列中放置皇后时,我们检查是否与已经放置的皇后发生冲突。在当前列中,如果找到没有冲突的行,则将该行和列标记为解决方案的一部分。如果由于冲突而没有找到这样的行,那么我们将回溯并返回false。
1)从最左边的一栏开始
2)如果所有皇后都已就位,返回true
3)尝试当前列中的所有行。每试一行都要跟着做。
a)如果皇后可以安全地放置在这一行,那么将这[行,列]标记为解决方案的一部分,并递归检查是否放置蚁后找到了解决办法。
b)如果将queen放在[row, column]中会得到一个解,则返回true。
c)如果放置queen不能找到解决方案,那么取消这一行、这一列的标记(回溯),转到步骤(a)尝试其他行。
3)如果所有行都尝试过,但没有任何结果,返回false以触发回溯。
总结
回溯是递归的一种形式。通常的情况是,您面临许多选项,您必须选择其中之一。在你做出选择之后,你会得到一组新的选项;你得到的选择取决于你做出的选择。这个过程会重复一遍又一遍,直到你达到最后的状态。如果你做了一系列的选择,你的最终状态就是目标状态;如果你没有,那就不是。从概念上讲,你从树的根部开始;这棵树可能有一些好叶子和一些坏叶子,虽然可能是所有的叶子都是好的或坏的。你想要得到一片好叶子。在每个节点上,从根节点开始,选择要移动到的子节点之一,并一直这样做,直到到达叶子节点为止。假设你得到一片坏叶子。您可以通过撤销最近的选择,并尝试该选项集中的下一个选项,从而回溯到继续搜索好叶子。如果您耗尽了选项,请撤消将您带到这里的选项,并在该节点上尝试另一个选项。如果您最终在根目录中没有留下任何选项,那么就没有好的叶子可以找到。
1.从根开始,您的选项是A和b。
2.你的选项是C和d,你选择C。
3.C是不好的。回到A。
4.在A,你已经尝试过C了,
5.但是失败了。试着D。
6.D是不好的。回到A。
7.在A,你没有选择的余地。回到根结点。
8.从根本上说,你已经试过了,尝试B。
9.在B,你的选择是E和f,试试E。
10.E是好的。恭喜你!
在这个例子中,我们画了一棵树。这棵树是我们可能做出的一系列选择的抽象模型。还有一种数据结构称为树,但通常我们没有数据结构来告诉我们有什么选择。(如果我们确实有一个实际的树数据结构,对其进行回溯称为深度优先树搜索。)
More Backtracking Problems:
- Backtracking | Set 1 (The Knight’s tour problem) (骑士之旅问题)
- Backtracking | Set 2 (Rat in a Maze) (迷宫中的老鼠)
- Backtracking | Set 4 (Subset Sum) (子集和)
- Backtracking | Set 5 (m Coloring Problem) (m着色问题)
原文链接:Backtracking
相关文献:backtracking