回溯法

基本思想

定义:以深度优先方式系统(全面)搜索问题的算法。

解空间表示:目的在于直观地看到所有解(包含最优解),把问题的所有可能解通过树或者图的方式列出,这样容易理解,且方便针对性的确定搜索方法。

回溯:以树为例,从一个结点出发深度遍历(根左右),每到一个结点就判断是否符合题目要求,若符合就继续,当遍历到叶子节点时就对比并记录下当前最优结果;若不符合,就直接返回上一结点遍历另一分支。

剪枝函数:用来判断是否继续沿当前结点遍历,避免无效搜索。通常有两种策略:(1)约束函数,剪去不满足约束的子树;(2)限界函数,剪去得不到最优解的子树。

算法步骤

  1. 针对所给问题,定义问题的解空间;
  2. 确定易于搜索的解空间结构;
  3. 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

两种回溯

(1)递归回溯

void backtrack(int t){
    if(t>n) output(x);
    else
        for(int i=f(n,t);i<=g(n,t);i++){
            x[t]=h(i);
            if(constraint(t) && bound(t))
                backtrack(t+1);
        }
}

t:遍历的当前结点所在树深度,作为输入值,指定了从哪里开始遍历,一般传入1,搜索整个树;

n:空间树的最大深度;t>n表示已经搜到叶子结点,记录或输出该条搜索路径得到的解;

f(n,t)和g(n,t):分别代表当前结点的子树(没搜索过)的起始编号和终止编号;

h(i):表示在当前第i个子树,所选到的值;

x[t]:只记录当前在该层(t)的取值,所以可知当得到最优解,x中也只有最优解的路径;

constraint和bound:分别用来控制剪枝,决定是否继续延续该层该点往下遍历。

(2)迭代回溯

void iterativeBacktrack(){
    int t=1;
    while(t>0){
        if(f(n,t)<=g(n,t))
            for(int i=f(n,t);i<=g(n,t);i++){
                x[t]=h(i);
                if(constraint(t) && bound(t)){
                    if(solution(t)) output(x);
                    else t++;
                }
            }
        else t--;
    }
}

solution(t):用来判断是否到达叶子结点;

注意:搜索中我们要的是路径对应的值,不是结点,结点只用来连接。该值对应问题可选值。可以由下面的例子理解。

两种典型空间树

(1)子集树:所给问题的解是从n个元素的集合中,选出满足某种条件的子集。例:n个物品的0-1背包问题,有2^{n}个叶结点,结点总数2^{n+1}-1个,n=3时,子集树如图,路径值表示是否选择该物品。

回溯法_第1张图片

回溯法搜索子集树(二叉树)的算法:

void backtrack(int t){
    if(t>n) output(x);
    else
        for(int i=0;i<=1;i++){//只有左子树和右子树
            x[t]=i;
            if(constraint(t) && bound(t))
                backtrack(t+1);
        }
}

(2)排列树:所给问题的解包含所有的n个元素,只是要满足某种条件的排列。例:包含n个城市的旅行售货员问题,有n!个叶结点,n=4时,排列树如图,路径值表示是否走该城市。

回溯法_第2张图片

回溯法搜索排列树的算法:

void backtrack(int t){
    if(t>n)  output(x);
    else
        for(int i=t;i<=n;i++){
            swap(x[t],x[i]);
            if(constraint(t) && bound(t))
                backtrack(t+1);
            swap(x[t],x[i]);
        }
}

类似于数组全排列问题。

参考文献

[1] 算法设计与分析(第2版) 王晓东

你可能感兴趣的:(Java算法,回溯法)