回溯法算法学习
回溯算法的定义
回溯法(back tracking)(探索与回溯法)是一种选优搜索法,又称为试探法,有“通用的解题法”之称,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回到上一步,重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
回溯算法的思想
回溯法是一种试探性的算法,一边搜索带入值,一边判断被带入值的合法性,如果合法就在此基础上进一步搜索带入值,验证合法性,直到求出解。本质是搜索树,解是由根节点到叶子结点的路径,表现为n维向量。
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。由于在搜索过程中,放弃了一些没有必要搜索的结点,整个算法的效率就提高了.
- 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
- 若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
图的着色搜索树为例,1、2、3代表红、蓝、黑三种颜色,虚线代表结点不能取色的颜色:
回溯算法的使用条件
回溯法需要满足多米诺性质,该性质证明了回溯法的合理性,是回溯法不丢解的依据。
多米诺性质:
如果当前结点不满足约束条件,能够推导出它的子结点也不满足约束条件。即解的前k个分量不满足条件时,那么前k+1个分量也不满足条件,于是我们可以在选择第k个分量时停止其第k+1个分量的探索。考察多米诺性质能够保证回溯时不会丢解。
例子:
回溯算法的实现方式
递归实现
思路简单,设计容易,但效率低,其设计范式如下:
//针对N叉树的递归回溯方法
void backtrack(int t) {
if (t > n) output(x); //叶子节点,输出结果,x是可行解
else
for i = 1 to k //当前节点的所有子节点
{
x[t] = value(i); //每个子节点的值赋值给x
//满足约束条件和限界条件
if (constraint(t) && bound(t))
backtrack(t + 1); //递归下一层
}
}
迭代实现
算法设计相对复杂,但效率高。
//针对N叉树的迭代回溯方法
void iterativeBacktrack() {
int t = 1;
while (t > 0) {
if (ExistSubNode(t)) //当前节点的存在子节点
{
for i = 1 to k //遍历当前节点的所有子节点
{
x[t] = value(i); //每个子节点的值赋值给x
if (constraint(t) && bound(t)) //满足约束条件和限界条件
{
//solution表示在节点t处得到了一个解
if (solution(t)) output(x); //得到问题的一个可行解,输出
else t++; //没有得到解,继续向下搜索
}
}
} else //不存在子节点,返回上一层
{
t--;
}
}
}
回溯算法解决的问题
问题描述:对于一组各不相同的数字,从中任意抽取1-n个数字,构成一个新的集合。
求出所有的可能的集合。例如,对于集合{1,2,3},其所有子集为{1},{2},{3},{1,2},{1,3},{2,3}{1,2,3},
给定一个数组(元素各不相同),求出数组的元素的所有非空组合(即数组的所有非空子集)
解法:位向量法。用一个辅助数组表示各个元素的状态。1表示在集合中,0表示不在数组中。递归地求解所有的子集。
搜索树:
算法描述如下://这里的算法对空集也输出了,可修改之使得只输出非空集合。
下面代码分别用递归回溯和迭代回溯的方法实现了算法
#include <iostream>
using namespace std;
//递归实现
void getSubset(int list[],bool v[],int a,int b){
if(a == b){
for(int i = 0; i < b; i++){
if(v[i])
cout<<list[i]<<" ";
}
cout<<endl;
return;
}
v[a] = true;
getSubset(list,v,a+1,b);
v[a] = false;
getSubset(list,v,a+1,b);
}
//迭代实现
void getSubset_iterative(int list[],int n){
int t = 0;//t 代表解空间树的深度
bool v[n+1] = {false,false,false,false};
int init[n+1] = {0,0};
while( t >= 0){
if(2 > init[t]){
init[t]++;
v[t] = !v[t];
if( t >= n ){
for(int i = 0; i <=n; i++){
if(v[i]) cout<<list[i]<<" ";
}
cout<<endl;
}
else{
++t;//进入下一层
}
}
else{
//回溯至上一层,并将该层的状态重置,一定要重置
init[t] = 0;
--t;
}
}
}
int main(){
int li[] = {1,2,3,4};
bool v[] = { false,false,false,false};
getSubset(li,v,0,4);
getSubset_iterative(li,3);
}