文章目录
1. 回溯算法
1.1 基本思想
回溯法的基本做法是搜索,在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
1.2 回溯法与分支界限法的异同
-
相同点:都是一种在问题的解空间树T中搜索问题解的算法。
-
不同点:
(1)求解目标不同;(2)搜索方式不同;(3)对扩展结点的扩展方式不同;(4)存储空间的要求不同。
2. 典型案例
2.1 装载问题
2.1.1 问题描述
给定n个集装箱要装上一艘载重量为c的轮船,其中集装箱i的重量为wi。集装箱装载问题要求确定在不超过轮船载重量的前提下,将尽可能多的集装箱装上轮船。
由于装载问题是从n个集装箱里选择一部分集装箱,假设解向量为X(x1, x2, …, xn),其中xi∈{0, 1}, xi =1表示集装箱 i 装上轮船, xi =0表示集装箱 i 不装上轮船。
数据案例为 c=34 n=3 重量分别为 21、20、5 时的子集树如下:
2.2 .2 约束函数与上界函数
-
约束函数:cw+w[i] < c
-
上界函数:cw+r <= bestw
cw:当前轮船的载重量 w[i]:第 i 个集装箱的重量
c:轮船的载重量 bestw:当前最有载重量
r :剩余集装箱的重量(注意,不是轮船的剩余载重量),比如说上图中处于结点E时,第一个集装箱装入,第二个不装入,那么 r 的值就等于剩余最后一个集装箱的载重量5;
那么已经确定装入的集装箱重量cw加上剩下所欲集装箱的重量为:cw + r = 21 + 5 = 26。
约束函数表示,处于某个当前结点时,当前的cw值加上下一集装箱的重量要小于轮船的载重量,否则就不能装入轮船,进行剪枝。
上界函数表示,当前结点的cw值加上剩余轮船载重量r所能得到的重量要大于当前bestw值,否则即使再从右子树向下搜索,也得不到大于当前bestw的值。
2.2.3 举例解释
n=4, c=12, w=[8, 6, 2, 3],bestw初值=0。
重点解释约束和上界函数剪枝:
约束函数:对于结点B,扩展结点D时,D的cw=14,此时大于轮船载重量c,因此不在进行左子树的扩展,进行剪枝。
上界函数:对于结点C,在进行右子树G扩展时,G结点的cw=0,bestw = 11,r =2+3 = 5(第一个和第二个集装箱不装入,后面的全部装入),所以cw+r < bestw,则不在进行右子树的扩展,剪去结点G。
2.2.4 复杂度
- 时间复杂度:O(2^n)
2.2 n皇后问题
2.2.1 思路总结
在对n个皇后向nxn棋盘放置的过程,就相当于对1,2,3…,n个皇后针对棋盘每一行的序列进行全排列,在这些全排列中只有一部分符合约束调件,这些符合约束条件的排列就是可行方案。
2.2.2 代码实现
/*
@author cc
@date 2021/4/26
@Time 10:03
To change this template use File | Settings | File Templates.
回溯算法解决n后问题
*/
#include "iostream"
using namespace std;
#define NUM 20
int n; // n为皇后的个数,以及棋盘为nxn大小
int x[NUM]; // 第NUM列皇后所在x[NUM]行
int sum; // 当前已找到的可行方案数
// 约束函数,实现对皇后位置的检查
// 形参t是回溯的深度
inline bool Place(int t){
int i;
for (i = 1; i < t; ++i)
// abs(t-i) == abs(x[i]-x[t]) 用来判断皇后是否在同一条对角线上
// x[i] == x[t] 用来判断皇后是否在同一行上
if ( ((abs(t-i)) == abs(x[i]-x[t])) || (x[i] == x[t]) )
return false; // 如果两个皇后在同一对角线或同一行则返回false
return true; // 如果都不在就放回true
}
// 形参t是回溯的深度,从1开始
void Backtrack(int t){
int i;
// 回溯深度大于皇后的个数时,获得一个可行方案
if (t>n){
sum++;
for (i = 1; i <= n ; ++i) {
cout << x[i] << " ";
}
cout << endl;
} else{
// 将皇后从棋盘每行的第一个位置开始防止,放置完成对其位置进行检查,
// 如果与之前的皇后不冲突,则进行下一行皇后位置的放置,即另t+1跳转到下一行,继续从第一个位置开始,直到回溯的深度大于n,就获得到一个可行方案
// 然后进行回退,返回到上一层,另i继续增加,即皇后的位置在上一行中继续向后移动,探寻下一个可行方案,直到回退到顶层
for (i = 1; i <= n; ++i) {
x[t] = i;
if (Place(t)) Backtrack(t+1);
}
}
}
int main(){
cout << "请输入皇后的个数:" << endl;
cin >> n;
Backtrack(1);
cout << "总的可行方案为:" << sum << "个" << endl;
return 0;
}