简单学习回溯法,理解回溯法的原理,解题步骤,解空间树等问题
概念
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
这种以深度优先方式系统搜索问题解的算法称为回溯法,适合解组合数比较大的问题。
回溯算法的基本步骤
1.定义问题的解空间;
解空间(解空间树):一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列。
解空间的求解过程:
可行解:解空间中满足约束条件的解空间。
最优解:使得目标函数取得最大或者最小值的可行解。
2.确定易于搜索的空间结构;
3.以深度优先的方式搜索解空间,并且在搜索的过程中使用剪枝函数避免无效搜索;
回溯算法要点
1.深度优先搜索
2.剪枝函数:
- 用约束函数减去在扩展节点处减去不满足约束的子树;
- 用界限函数减去得不到最优解的子树;
回溯法的算法框架
1.迭代回溯框架
void iterativeBacktrack(void) {
int t = 1;//表示当前的决策层在解空间中
while (t > 0) {//算法一直回溯,直到找到一个解或者没有解为止
if (f(n, t) < g(n, t)) {//检查函数f和g的返回值。如果f(n, t)的值小于g(n, t)的值,则执行下面的代码块
for (int i = f(n, t); i < g(n, t); ++i) {//从f(n, t)开始,到g(n, t)结束。在每次迭代中,它都会尝试不同的决策
x[t] = h(i);//对于当前的决策值i,将x[t]设置为函数h(i)的返回值。这可能是在设置一个变量或值来构建当前的解
if (conStraint(t) && boundStraint(t)) {//检查当前解是否满足约束条件(由conStraint(t)表示)和边界条件(由boundStraint(t)表示)
if (solution(t)) outPut(x);//如果当前解是一个有效的解,则输出该解。这里假设有一个函数outPut(x)来输出或返回找到的解。
else t++; //如果当前解不是有效的解,则增加t的值,尝试在更高层的决策空间中寻找解
}
}
else t--;//如果函数f(n, t)的值大于或等于函数g(n, t)的值,则减少t的值,尝试在更低层的决策空间中寻找解。
}
}
2.递归回溯框架
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)&&boundStraint(t)){//如果满足边界条件和约束条件就继续
backTrack(t+1);//满足条件,处理下一个决策层
}
}
}
}
案例1:图的m着色问题
1.问题描述:给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色,是否有一种着色法使G中任意相邻的2个顶点着不同颜色。
2.简单示例分析:5个区域,四种颜色,着色方案?
3.解空间树:顶点数n=5,颜色种数m=4,问题的解空间可以表示为一刻高度为n+1=6,完全m=4叉树。如下图:
4.算法设计:
- 深度优先遍历
- 剪枝函数:约束函数、界限函数
if ((a[k][j]==1)&&(x[j]==x[k])),即判断顶点j是否与顶点k相邻且颜色相同,
如果存在,不符合要求,退出循环。否则,继续遍历。
5.代码实现:
void Backtrack(int t){
if (t>n){// 算法搜索到叶子结点
sum++;
for (int i=1; i<=n; i++)
cout << x[i] << " ";
cout << endl;
}
else{
//在每一层的遍历过程中,每一个结点都有m种颜色的选择
for (int i=1;i<=m;i++) {
x[t]=i;//存储解向量
if (Ok(t)) //如果没有被减枝就进行下一步操作
Backtrack(t+1);
x[t]=0;
}
}
}
bool Ok(int k) { // 检查颜色可用性
for (int j=1;j<=n;j++){
if ((a[k][j]==1)&&(x[j]==x[k])) { //相邻且颜色相同
return false;
}
}
return true;
}