回溯法(backtracking)
回溯法,是一种系统地搜索问题的方法。其算法思想有点类似于树的深度遍历。
在算法设计策略中,回溯法是比贪心法和动态规划更一般的方法。对于这样一类问题,其解可以表示成一个n-元组,求满足约束条件的可行解,或进一步求使目标函数取最大或最小值得最优解问题,其中许多问题都可以用回溯法求解。回溯法是一种通过搜索状态空间树来求问题的可行解或最优解的过程。回溯法使用约束函数和限界函数来压缩需要实际生产的状态空间树的节点数,从而大大节省问题求解时间。
回溯法的本质上是一种以深度优先方式,逐一生成状态空间树中节点并检测答案节点的方法。与穷举法不同,回溯法使用约束函数,剪去那些可以断定不含答案状态的子树,从而提高算法效率。
简单的理解就是:向下遍历,不含答案时,向上退回换路。
8皇后问题
问题描述:在一个8*8的棋盘上面放置8个皇后,使每两个皇后不相互攻击,即任意两个皇后都不能位于同一行、同一列或者同一对角线上。
#include <iostream>
#include <algorithm>
#include <iterator>
//打印
int s=0;
void print_seq(int *data,int num)
{
std::copy(data,data+num,std::ostream_iterator<int>(std::cout," "));
std::cout<<std::endl;
++s;
}
//判断data的第i行第j列可不可以放
bool able_place(int *data,int i,int j)
{
for(int k=0;k<i;++k)
if(j==data[k]||abs(k-i)==abs(data[k]-j)) return false;//保证任意两个皇后不能位于同一列、同一对角线上
return true;
}
//从第i行开始放
void place_queens(int *data,int num,int i)
{
for(int k=0;k<num;++k)//讨论放在每一列
{
if(able_place(data,i,k) )//i行k列可放
{
data[i]=k;//放置
if(i==num-1) print_seq(data,num);//如果放满,打印
else place_queens(data,num,i+1); //如果没放满,继续放
//如果非要回溯清晰,我们在这儿可以加 :data[i]=-1;
}
}
}
int main(int argc, char** argv) {
const int num=8;
int data[num];//表示第0至num-1行放的位置
place_queens(data,num,0);//8皇后
std::cout<<"共有:"<<s<<"种"<<std::endl;
return 0;
}
子集与数问题
问题描述:给定n个互不相等的正整数集合P及一个正数S,求整数和为S的P的所有子集。
下面给出代码,尽管效率不是最高的,但是是比较容易理解的一种。
#include <iostream>
const int p[6]={5,10,12,13,15,18};
const int S=30;
bool x[6]={0};
void if_print_seq()
{
for(int i=0;i<6;++i)
if(x[i]) std::cout<<p[i]<<" ";
std::cout<<std::endl;
}
//index表示遍历到的元素,sum表示已经保留的元素的和
void subset_set(int index,int sum)
{
if(index<6)
{
if(p[index]+sum==S) //表示正好取到和为S的情况
{
x[index]=1;
if_print_seq();
}else if(p[index]+sum>S) //表示取到和大于S的情况
{
x[index]=0;//不取
subset_set(index+1,sum);
}
else if(p[index]+sum<S)//表示取到和小于S的情况
{
x[index]=1;//可以取
subset_set(index+1,p[index]+sum);
x[index]=0;//也可以不取
subset_set(index+1,sum);
}
}
}
int main(int argc, char** argv) {
subset_set(0,0);
return 0;
}