**回溯法(Backtracking):基本做法是搜索,是一种避免不必要搜索的穷举式搜索法。
-
有”通用的解题法“之称
-
按深度优先策略,从根结点出发搜索解空间树
-
根据剪枝函数来避免无用搜索
回溯法设计过程:
- 确定问题的解空间
- 常见解空间:排列树和子集树
- 确定结点的扩展规则
- 搜索解空间
N 皇后问题
要在n*n的国际象棋棋盘中放n个皇后,使任意两个皇后都不能互相攻击。皇后能吃掉同一行、同一列、同一对角线的任意棋子。
暴力法求解需遍历 种可能,过于庞大,因此使用回溯法可以大量地剪掉那些不必要的搜索。
这里以八皇后问题为例,设八个皇后为 xi,分别在第 i 行,因此解可以用一个 8 个单位的数组表示。
问题的解空间:(x1, x2, x3, x4, x5, x6, x7, x8),共有 88 个可能,是一棵排列树
约束条件:可以整合成: and
- 不在同一列:;不在同一主对角线上:;不在同一负对角线上:
1 | bool check(int a[], int n) { |
使用递归的实现:
1 | int main(){ |
01背包问题
解空间:x[n] 数组,表示第 i 个物品是否选择,是一棵子集树
剪枝函数:
-
curWeight + w[i] > W,背包装不下则剪去左子树
-
rest + curValue <= bestValue,剩余价值加上当前价值都小于最优解,因此剪去右子树
1 | backtrack(int k) { |
时间复杂度:
子集和
解空间:是否选择当前数字,是一棵子集树
剪枝函数:
- sum + W[k] = M,则为一个解
- sum + W[k] + W[k+1] <= M,进入左子树
- sum + rest - W[k] >= M and sum + W[k+1] <= M,进入右子树
TSP
解空间:给出n个点,以0为起点,构造一个排列树,共有(n-1)!的叶子结点,每次选择剩下的元素中的一个,从根到叶结点表示一条可选路径
剪枝条件:
- 判断当前路径是否连通,连通继续,不连通剪掉
- 判断当前路径是否比已经求得的路径小,小于继续,大于等于剪掉
估值&检查机制
- 好的机制能显著地减少所生成的结点数
- 但往往计算量大
- 折衷问题:“剪枝”节约的 vs.估值检查机制消耗的
回溯算法的效率很大程度依赖于:
- 产生一个 x[k] 的时间
- 过程中经过检查的x[k]的个数
- 计算估值(约束)函数的时间代价
递归的算法框架
1 | int a[n]; // 解的表达式形式 |