【算法Part2】回溯算法

一.概念

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

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

  许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

二.基本思想

  回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

  • 算法在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。
  • 回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
  • 回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。(摘自回溯算法

三.解题步骤

  1. 针对所给问题,确定问题的解空间:
    首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解
  2. 确定结点的扩展搜索规则
  3. 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索

四.算法框架

bool finished = FALSE; /* 是否获得全部解? */
backtrack(int a[], int k, data input)
{
    int c[MAXCANDIDATES]; /*这次搜索的候选 */
    int ncandidates; /* 候选数目 */
    int i; /* counter */
    if (is_a_solution(a,k,input))
    process_solution(a,k,input);
    else {
        k = k+1;
        construct_candidates(a,k,input,c,&ncandidates);
        for (i=0; i<ncandidates; i++) {
            a[k] = c[i];
            make_move(a,k,input);
            backtrack(a,k,input);
            unmake_move(a,k,input);
            if (finished) return; /* 如果符合终止条件就提前退出 */
        }
    }
}

变量解释:

  • a[]表示当前获得的部分解;
  • k表示搜索深度;
  • input表示用于传递的更多的参数;
  • is_a_solution(a,k,input)判断当前的部分解向量a[1...k]是否是一个符合条件的解
  • construct_candidates(a,k,input,c,ncandidates)根据目前状态,构造这一步可能的选择,存入c[]数组,其长度存入ncandidates
  • process_solution(a,k,input)对于符合条件的解进行处理,通常是输出、计数等
  • make_move(a,k,input)unmake_move(a,k,input),前者将采取的选择更新到原始数据结构上,后者把这一行为撤销。

五.题型解答

1. 求一个集合的所有子集(leetcode.78)

//给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)
class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    
    void backtrace(vector<int>& nums,int start){
        res.push_back(temp);
        for(int i = start;i < nums.size();++i){
            temp.push_back(nums[i]);
            backtrace(nums,i+1);
            temp.pop_back();
        }
    }
    
    vector<vector<int>> subsets(vector<int>& nums) {
        if(!nums.size())
            return res;
        backtrace(nums,0);
        return res;
    }
};

2.电话号码的字母组合(leetcode.17)

/*
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
*/
class Solution {
public:
    map<char, string> mp = { { '2', "abc" }, { '3', "def" }, { '4', "ghi" }, { '5', "jkl" },
                             { '6', "mno" }, { '7', "pqrs" }, { '8', "tuv" }, { '9', "wxyz" } };
    vector<string> res;
    string temp;

    void backtrace(string digits, int start){
        if (!digits.size())
            res.push_back(temp);
        else{
            char num = digits[0];
            string letter = mp[num];
            for (int i = 0; i<letter.size(); i++){
                temp.push_back(letter[i]);
                backtrace(digits.substr(1), i + 1);
                temp.pop_back();
            }
        }
    }

    vector<string> letterCombinations(string digits) {
        if (!digits.size())
            return res;
        backtrace(digits, 0);
        return res;
    }
};
posted @ 2020-08-03 20:35  KJee  阅读(183)  评论(0编辑  收藏  举报