为了描述问题的某一状态,必须用到该状态的上一状态,而描述上一状态,又必须用到上一状态的上一状态……这种用自已来定义自己的方法,称为递归。
从问题的某一种可能出发, 搜索从这种情况出发所能达到的所有可能, 当这一条路走到” 尽头 “的时候, 再倒回出发点, 从另一个可能出发, 继续搜索. 这种不断” 回溯 “寻找解的方法, 称为回溯。
回溯法是以深度优先方式系统搜索问题解的算法,适用于解决组合数较大的问题。
回溯就是让计算机自动的去搜索,碰到符合的情况就结束或者保存起来,在一条路径上走到尽头也不能找出解,就回到原来的岔路口,选择一条以前没有走过的路继续探测,直到找到解或者走完所有路径为止。
回溯就是一种试探,类似于穷举,但回溯有“剪枝”功能。
回溯法一般有两种实现方式,分别是递归回溯和迭代回溯。
回溯一般使用递归来实现,那个这个递归调用该如何来写呢?进行回溯搜索都会有一系列的步骤,每一步都会进行一些查找。而每一步的情况除了输入会不一样之外,其他的情况都是一致的。这就刚好满足了递归调用的需求。通过把递归结束的条件设置到搜索的最后一步,就可以借用递归的特性来回溯了。
回溯常用模板:
1.非递归模板:
1: int a[n],i; 2: 初始化数组a[]; 3: i = 1; 4: while (i>0(有路可走) and (未达到目标)) // 还未回溯到头 5: { 6: if(i > n) // 搜索到叶结点 7: { 8: 搜索到一个解,输出; 9: } 10: else // 处理第i个元素 11: { 12: a[i]第一个可能的值; 13: while(a[i]在不满足约束条件且在搜索空间内) 14: { 15: a[i]下一个可能的值; 16: } 17: if(a[i]在搜索空间内) 18: { 19: 标识占用的资源; 20: i = i+1; // 扩展下一个结点 21: } 22: else 23: { 24: 清理所占的状态空间; // 回溯 25: i = i –1; 26: } 27: }
2.递归模板:
1: int a[n]; 2: try(int i) 3: { 4: if(i>n) 5: 输出结果; 6: else 7: { 8: for(j = 下界; j <= 上界; j=j+1) // 枚举i所有可能的路径 9: { 10: if(fun(j)) // 满足限界函数和约束条件 11: { 12: a[i] = j; 13: ... // 其他操作 14: try(i+1); 15: 回溯前的清理工作(如a[i]置空值等); 16: } 17: } 18: } 19: }
3.迭代回溯伪代码:
void IterativeBacktrack(void){ int t = 1; while(t > 0){ if(f(n,t) < g(n,t)){ for(int i = f(n,t); i <= g(n,t); ++i){//这个for 是遍历各个值的意思,实际中写成for循环会有逻辑错误 x[t] = h(i); if(constraint(t) && bound(t)){ if(solution(t)) Output(x);//solution 判断是否已经得到问题的解 else t++; } else t--; } } } }
4.递归回溯伪代码:
void Backtrack(int t){ if(t > n) Output(x);//Output 记录或者输出可行解 else{ //f(n,t)和g(n,t)表示在当前结点未搜索过的子树的起始编号和终止编号 for( int i = f(n,t); i <= g(n,t); ++i){ x[t] = h(i); //constraint和bound分别是约束函数和界限函数 if(constraint(t) && Bound(t)) Backtrack(t+1); } } }