📜  回溯|介绍

📅  最后修改于: 2021-04-27 20:24:58             🧑  作者: Mango

先决条件

  • 递归
  • 复杂度分析

回溯是一种算法技术,它通过尝试逐步构建一个解决方案来一次递归地解决问题,并删除那些在任何时间点都无法满足问题约束的解决方案(此处指的是时间)达到搜索树的任何级别所花费的时间)。

根据Wiki的定义,

回溯中存在三种类型的问题–

  1. 决策问题–我们在此寻找可行的解决方案。
  2. 优化问题–我们在此寻找最佳解决方案。
  3. 枚举问题–在此,我们找到了所有可行的解决方案。

如何确定使用Backtracking是否可以解决问题?

通常,每个约束满足问题在任何客观解决方案上都有明确的定义明确的约束,一旦确定候选对象无法完成有效的任务,就会逐步将候选对象构建到该解决方案中并放弃候选对象(“回溯”)解决方案,可以通过回溯解决。但是,大多数讨论的问题都可以使用其他已知算法(例如动态规划贪婪算法)以对数,线性,线性对数的时间复杂度按输入大小的顺序来解决,因此在各个方面都不如回溯算法(因为回溯算法通常在时间和空间上都是指数的)。但是,仍然存在一些问题,直到现在为止,只有回溯算法才能解决这些问题。

考虑一种情况,您面前有三个盒子,只有一个盒子里有一个金币,但您不知道哪个盒子。因此,为了获得硬币,您将必须一一打开所有盒子。您将首先选中第一个框,如果其中不包含硬币,则必须将其关闭并选中第二个框,依此类推,直到找到硬币为止。这就是回溯的意思,就是要逐一解决所有子问题,以达到最佳解决方案。

考虑以下示例,以更正式地了解“回溯”方法,

给定任何计算问题的实例P  和数据D  对应于实例,解决该问题需要满足的所有约束都表示为C  。回溯算法将按以下方式工作:

该算法开始建立一个解决方案,从一个空的解决方案集开始S  S = {}

  1. 添加S  剩下的第一个动作(所有可能的动作已添加到S  逐个)。现在,这将创建一个新的子树s  在算法的搜索树中。
  2. 检查是否S+s  满足C
    • 如果是,则子树s  有资格添加更多“孩子”。
    • 否则,整个子树s  是没有用的,因此使用参数返回到第1步S
  3. 如果新形成的子树具有“资格” s  ,使用参数返回到第1步S+s
  4. 如果检查S+s  返回它是整个数据的解决方案D  。输出并终止程序。
    如果不是,则返回当前解决方案是不可能的s  因此将其丢弃。

递归和回溯之间的区别:

在递归中,该函数将自行调用,直到达到基本情况为止。在回溯中,我们使用递归来探索所有可能性,直到获得针对该问题的最佳结果。

回溯的伪代码

1.递归回溯解决方案。

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);

2.查找解决方案是否存在

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 Queen是在N×N棋盘上放置N个国际象棋皇后的问题,这样就不会有两个女王互相攻击。例如,以下是4 Queen问题的解决方案。

预期的输出是一个二进制矩阵,其中放置了皇后的块的二进制数为1。例如,以下是上述4个皇后解决方案的输出矩阵。

{ 0,  1,  0,  0}
{ 0,  0,  0,  1}
{ 1,  0,  0,  0}
{ 0,  0,  1,  0}

回溯算法:想法是将皇后区从最左边的列开始一一放置在不同的列中。将皇后放置在列中时,我们检查是否与已放置的皇后发生冲突。在当前列中,如果找到没有冲突的行,则将该行和列标记为解决方案的一部分。如果由于冲突而找不到这样的行,那么我们将回溯并返回false。

1) Start in the leftmost column
2) If all queens are placed
    return true
3) Try all rows in the current column.  Do following for every tried row.
    a) If the queen can be placed safely in this row then mark this [row, 
        column] as part of the solution and recursively check if placing  
        queen here leads to a solution.
    b) If placing the queen in [row, column] leads to a solution then return 
        true.
    c) If placing queen doesn't lead to a solution then unmark this [row, 
        column] (Backtrack) and go to step (a) to try other rows.
3) If all rows have been tried and nothing worked, return false to trigger 
    backtracking.

您可以参考“回溯”中的文章|设置3(N Queen问题),以完全实现上述方法。
更多回溯问题

  • 回溯|第一组(骑士的旅行问题)
  • 回溯|套装2(迷宫中的鼠)
  • 回溯|组合4(子集总和)
  • 回溯|一组5(m着色问题)
  • –>单击此处了解更多