reference:
http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html
http://www.cnblogs.com/Creator/archive/2011/05/20/2052341.html
1、什么是回溯法
回溯法是一种系统地搜索问题解答的方法。在搜索的过程中尝试找到问题的解,如果发现找不到了,就退一步,往上回溯(剪枝过程)。对于许多复杂问题和大规模问题都可以使用回溯法。
回溯法的基本思想是按照深度优先搜索的策略,从根节点开始搜索,当到某个节点时要判断是否是包含问题的解,如果包含就从该节点继续搜索下去,如果不包含,就向父节点回溯。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
回溯法常用的剪枝函数:(1)约束函数:在节点处减去不满足约束的子树。(2)界限函数:减去得不到最优解的子树
2、回溯法解题的一般步骤
- 针对所给问题,确定问题的解空间
- 利用适于搜索的方法组织解空间
- 利用深度优先搜索解空间
- 在搜索过程中用剪枝函数避免无效搜索。
3、算法框架
- 设问题的解是一个n维向量(a1,a2,………,an),约束条件是ai(i=1,2,3,…..,n)之间满足某种条件,记为f(ai)。
- 非递归回溯框架
int a[n], i;
初始化a[n];
i = 1;
while (i > 0(有路可走) and (未达到目标))//还没有回溯到头
{
if (i > n)
{
搜索到一个解,输出;
}
else
{
a[i]第一个可能的值;
while (a[i]在不满足约束条件且在搜索空间内)
{
a[i]下一个可能的值;
}
if (a[i]在搜索空间内)
{
标识占用的资源;
i = i + 1; //扩展下一个节点
}
else
{
清理所占的空间状态; //回溯
i = i - 1;
}
}
}
- 递归回溯框架
int a[n];
try(int i)
{
if(i>n)
输出结果;
else
{
for(j = 下界; j <= 上界; j=j+1) // 枚举i所有可能的路径
{
if(fun(j)) // 满足限界函数和约束条件
{
a[i] = j;
... // 其他操作
try(i+1);
回溯前的清理工作(如a[i]置空值等);
}
}
}
}
4举例:N皇后问题
在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。
/* 用n元组x[1:n]表示n后问题的解。x[i]表示皇后i放置在棋盘的第i行的第x[i]列 */
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
static int n, x[1000];
static long sum;
/* 判断第k个后能不能放在x[k]处 两个皇后不能放在统一斜线上: 若2个皇后放置的位置分别是(i,j)和(k,l), 且 i-j = k -l 或 i+j = k+l,则说明这2个皇后处于同一斜线上。 */
void OutPut()
{
for (int i = 1; i <= n; ++i)
printf("(%d, %d) ", i, x[i]);
printf("\n");
}
int Place(int k)
{
for (int j = 1; j < k; ++j)
if (abs(k - j) == abs(x[k] - x[j]) || x[j] == x[k])
return 0;
return 1;
}
void BackTrack1(int t)
{
//如果t>n说明已经完成一次放置
if (t > n)
{
sum++;
OutPut();
}
else
{
for (int i = 1; i <= n; ++i)
{
x[t] = i;
if (Place(t)) //可以放在i位置处,则继续搜索
BackTrack1(t + 1);
}
}
}
void BackTrack()
{
int k;
x[1] = 0; //初始化为0
k = 1;
while (k >= 1) //循环
{
x[k] += 1; //先放在第一个位置
while ((x[k] <= n) && !(Place(k))) //如果不能放
x[k] += 1; //放在下一个位置
if (x[k] <= n) //放置完成
{
if (k == n) //如果已经放完了n个皇后
{
sum++; //处理次数,输出
OutPut();
}
else //没有处理完,让k自加,处理下一个皇后
{
k++;
x[k] = 0;
}
} //x[k] > n,说明没有合适的位置了
else
k--; //回溯回去,回到第k-1步
}
}
int main()
{
clock_t start, finish;
double duration;
int nn;
while (scanf_s("%d", &nn) != EOF)
{
n = nn;
sum = 0;
for (int i = 0; i <= n; ++i)
x[i] = 0;
BackTrack();
//BackTrack1(1);
printf("%d\n", sum);
}
return 0;
}