回溯算法学习笔记

回溯是一种系统的遍历所有搜索空间中可能配置的方法。这些配置可以代表对象所有可能的排列(permutation)或所有可能构造子集(subset)的方式。其他的情况可能是:枚举一个图的所有生成树、两个顶点间的所有路径或所有将顶点按颜色分类的可能的方式。

这些问题的共同点在于我们必须生成每一个可能的配置,并且不能重复也不能遗漏。这意味着我们得定义一个系统的生成顺序。

在回溯算法的每一步中,我们尝试通过在尾部添加另一个元素来扩展一个已有的部分解。扩展之后,我们必须测试我们得到的是否是一个解。如果是,则采用它,否则,我们必须检查之前那个部分解有没有可能以其他方式扩展成完整的解。

回溯构造了一个部分解的树,其中的每一个节点代表一个部分解。如果一个部分解由另一个部分解直接扩展得到,则代表两者的节点之间存在一条边。这个部分解的树为我们提供了一个理解回溯问题的新方式,因为构造解的过程完全与对这个回溯树的DFS过程一致。将回溯视为对一个隐式图的DFS自然而然的引出了基本算法的递归实现。

Backtack-DFS(A,k)
    if A = (a1,a2,...,ak) is a solution, report it.
    else
       k = k+1
       compute Sk
       while Sk != empty do
         ak = an element in Sk
         Sk = Sk - ak
         Backtrack-DFS(A,k)

尽管BFS也能用于枚举所有解,DFS因其占用更少空间而更被青睐。当前的搜索状态完全对应由跟到当前搜索的深度优先节点的路径。所需的空间与树的高度成比例。在BFS中,队列存储当前层中的所有节点,所需空间与树的宽度成比例。问题在于,树的宽度按其高度指数的增长。

实现:

bool finished = false;//用于提前结束

void backtrack(int a[], int k, data input)
{
    int c[MAXCANDIDATES];//未遍历的元素数组。每个递归过程都有独立的c,不会互相干扰
    int ncandidates;
    int i;

    if (is_a_solution(a, k, input))     //测试是否构成解
          process_solution(a, k, input);
    else{
          k = k+1;
          construct_candidates(a, k, input, c, &ncandidates);//对c赋值
          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; 
          }
     }
}

应用举例:
leetcode 17. Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> map = {"", "abc", "def", "ghi", "jkl", "mno",
                             "pqrs", "tuv", "wxyz"};

        vector<string> res;
        string str;

        int len = 0;
        for(auto digit : digits)
            if(digit != '1')
                len++;
        if(len == 0)
            return res;

        aux(str, res, map, digits, len, 0);
        return res;
    }

    void aux(string str, vector<string>& res, const vector<string>& map, const string& digits,
             int len, int i){
        if(str.size() == len) //解得满足一定的长度
            res.push_back(str);
        else{
            string tmp = map[digits[i]-'0'-1]; //construct_candidates
            for(auto t : tmp){
                auto tmp_str = str;
                tmp_str.push_back(t); //make move
                aux(tmp_str, res, map, digits, len, i+1);
                //str保持不变。unmake move
            }
        }
    }
};

参考资料:
《The Algorithm Design Manual》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值