1 回溯法
回溯法的基本做法就是搜索,或者是一种组织的井井有条的,能够避免不必要搜索的穷举式搜索。这种方法适合与解一些组合数相当大的问题。
回溯法在问题的解空间树中,按照深度优先策略,从根节点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该节点是否包含问题的解。如果肯定不包含,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索,即走不通就掉头。
求解问题所有解:要回溯到根,且根节点的所有子树都已被搜索遍才结束;
求解任一解:只要搜索到问题的一个解就结束。
2 回溯算法的设计过程
setp1:确定问题得解空间;
step2:确定节点的扩展规则;
step3:搜索解空间;
3 回溯法的应用-八皇后问题
问题描述:要在n*n的国际象棋棋盘中放n个皇后,使任意两个皇后都不能互相吃掉。规则:皇后能吃掉同一行,同一列,同一对角线的任意旗子。求所有的解。
n=8
设八皇后Xi,分别在第i行(i=1,2,3...,8);
问题的解状态:可以用(1,X1),(2,X2)....,(8,X8) 表示8个皇后的位置(由于行号固定,所以可以简单记为(X1,...,X8));
问题的解空间:(X1,...,X8),1<=Xi<=8;(i=1,2,3,...,8),一共8^8个状态;
约束条件:八个(1,X1),...,(8,X8)不在同一行,同一列,同一对角线上;
可以表示为:i)不在同一列上:Xi != Xj;ii)不在同一对角线上:Xi - i != Xj - 1,Xi + i !=Xj + j;将前面两种情形表示为:
(abs(Xi-Xj)==abs(i-j)) or (Xi==Xj)
使用盲目的枚举算法:
queen()
{
int a[9];
for(a[1]=1;a[1]<=8;a[1]++)
......
for(a[8]=1;a[8]<=8;a[8]++)
if(check(a,8)==0) continue;
else
for(i=1;i<=8;i++)
printf(a[i]);
}
check(a[],n)
{
int i,j;
for(int i=2;i<=n;i++)
for(int j=1;j<=i-1;j++)
if(a[i]==a[j] or (abs(a[i]-a[j])==abs(i-j)))
return 0;
return 1;
}
check()函数需要双重循环,任意两个皇后之间都必须检查。
1 使用递归回溯算法:
check1(int a[],int n)
{
int i;
for(i=1;i<=n-1;i++)
if(abs(a[i]-a[n])=abs(i-n) or (a[i]-a[n]))
return 0;
return 1;
}
int a[100],n;
main()
{
intput(n);
backtrack(1);
}
backtrack(int k)
{
if(k>n) 找到一组解输出结果
else
for(int i=1;i<=n;i++)
{
a[k]=1;
if(check1(a,k)=1) backtrack(k+1);
}
}
假设这里一共有4个皇后,如果k=3时,a[3]>4(即a[3]=1~4都不能满足check的条件)才能满足check1条件,从而就不会再往下执行(不会调k=4) 所以返回第二层,重新改变a[2]的值,即for循环中的a[2]++,然后check1,可行的话再向下执行(k=3)。
递归终止条件:当a[1](第一层)的取值大于4时,此时程序就不能再向下执行,所以程序终止。[其余的a[i]也是这个道理。只是a[i]是第一层,它终止之后递归解结束了]
2 非递归回溯算法
backdate(int n)
{
a[1]=1;k=1;
while(k>0){
a[k]++;
while((a[k]<=n)and (check1(a,k)=0)) //搜索第k个皇后的位置
a[k]++;
if(a[k]<=n)
if(k=n) output(n) //找到一组解
else{
k++; //继续为第k+1个皇后找到位置
a[k]=0; //注意下一个皇后一定要从头开始搜索
else
k--; //回溯
}
}
}
对a[k]>n,即不满足 a[k]<=n 表达式,说明a[k]已在棋盘外才能满足与前k-1个皇后不在同一对角线,行,列上。a[k]不满足所以需要进行回溯,即回到k-1个值a[k-1],然后重新计算a[k-1],得到之后再继续计算a[k](a[k]不包含,就跳过该节点为根的子树的搜索,逐层向其祖先节点(a[k-1])进行返回)。