回溯算法 解题思路

文章目录

  • 算法介绍
  • 回溯算法能解决的问题
  • 解题模板
  • 1. 组合问题
  • 2. N皇后问题

算法介绍

回溯法(Back Tracking Method)(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。

可以把回溯法看成是递归调用的一种特殊形式。

回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解

回溯算法能解决的问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

解题模板

代码方面,回溯算法的框架:

result = []
def backtracking(路径, 选择列表):
	// 确定终止条件
    if 满足结束条件:
        result.add(路径)
        return
	
	// 单层搜索
    for 选择 in 选择列表:
        做选择
        backtracking(路径, 选择列表)
        撤销选择

核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。

总结就是:

循环 + 递归 = 回溯

1. 组合问题

回溯算法 解题思路_第1张图片

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
            used[i] = false;
            sum -= candidates[i];
            path.pop_back();
        }
    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        path.clear();
        result.clear();
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
};

这个组合问题中,最主要的问题是搞清楚 树层去重树枝去重
回溯算法 解题思路_第2张图片

2. N皇后问题

回溯算法 解题思路_第3张图片

class Solution {
public:
    // 存放不同解法
    vector<vector<string>> result;
    void backtracking(int n, int row, vector<string> chessboard) {
        if(row == n) {
            result.push_back(chessboard);
            return;
        }
        for(int colu = 0; colu < n; colu++) {
            if(IsValid(n, row, colu, chessboard)) {  // 验证合法就可以放
                chessboard[row][colu] = 'Q';  // 放置皇后
                backtracking(n, row + 1, chessboard);
                chessboard[row][colu] = '.';  // 回溯,撤销皇后
            }
        }
    }

    bool IsValid(int n, int row, int colu, vector<string> chessboard) {
        // 检查放到此处是否有效, 同列、对角是否有其他皇后(回溯时每行只取一次,所以不用检查同行)

        // 向上检查同列是否有皇后
        for(int i = 0; i < row; i++) {
            if(chessboard[i][colu] == 'Q') {
                return false;
            }
        }
        // 检查 135°(左上)是否有皇后
        for(int i = row-1, j = colu - 1; i >= 0 && j >= 0; i--, j--) {
            if(chessboard[i][j] == 'Q') {
                return false;
            }
        }
        // 检查 45°(右上)是否有皇后
        for(int i = row-1, j = colu + 1; i >= 0 && j < n; i--, j++) {
            if(chessboard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }

    vector<vector<string>> solveNQueens(int n) {
        // 棋盘初始化
        vector<string> chessboard(n, string(n, '.'));
        // n:棋盘大小 0:从棋盘0行开始选
        backtracking(n, 0, chessboard);
        return result;
    }
};

你可能感兴趣的:(算法,回溯,数据结构和算法)