回溯算法有“通用解题”之称。用它可以系统地搜索所有的解。它既可以系统的搜索又可以跳跃式的搜索所有子集。
回溯算法主要有三点必须要彻底的弄清楚
【1】问题解空间的集合,合理的定义解空间集合
【2】确定易于搜索的解空间结构(通常按二叉树的深度遍历,或者图的形式来遍历解空间)
【3】采用深度遍历的方法,在搜索过程中用剪枝函数避免无效搜索
常见的回溯算法:
0-1背包问题
n后问题
旅行者售货员问题(暂时没看,下次再弄)
0-1背包问题
例如三种物品的0-1背包问题 1.首先确定其解空间是(0 0 0)(0 0 1)(0 1 0)(0 1 1)(1 0 0)(1 0 1)(1 1 0)(1 1 1)
通过二叉树的形式将解空间组织起来,采用深度遍历的方法遍历所有的解空间;
对于w[ 1 2 3 5 ] p[ 5 8 9 10 ] c=7 的0-1背包问题
void backtrack(int i, int n, int cp, int cw, int bestp, int *w, int *p, int c)//遍历的范围
{ if (i > n)
{ bestp = cp;return ;}
if (cw + w[i] <= c)
{ //进入左子树
cw += w[i]; cp += p[i];
backtrack(i + 1, n, cp, cw, bestp, w, p, c);
cw -= w[i];cp -= p[i];//回溯 }
if (bound(i+1,cw,c,cp,n,w,p) > bestp)//判断是否要剪枝
{ backtrack(i + 1, n, cp, cw, bestp, w, p, c); //进入右子树 }
}
int bound(int i,int cw,int c,int cp,int n,int *w,int *p)
{
int cleft = c - cw;
int bound = cp;
while (i <= n&&w[i] <= cleft)
{
cleft -= w[i];
bound += p[i];
++i;
}
if (i <= n)
{
bound += p[i] * cleft / w[i];
}
return bound;
}
n 皇后问题则更简单一点
每个皇后用一个数组的保存起来,通过约束条件舍弃分支,回溯到数组的上一个下标,若果可以顺利把所有皇后填入数组中,则该方案可行
皇后是保存在一维数组中,一维数组的下标与一维数组的值可以表示皇后在二维数组中的位置。
bool place(int t,int *x)
{
for (int j = 0; j < t; ++j)
{
if ((abs(t - j) == abs(x[j] - x[t])) || (x[j] == x[t])) return false;
}
return true;
}
void backtrack(int t,int n,int *x)
{
if (t >= n)
{
for (int i = 1; i < 5; ++i)
{
printf("%d", x[i]);
}
printf("
");
}
for (int i = 1; i <n; ++i)
{
x[t] = i; //x[1]=1.2.3.4
if (place(t,x)) backtrack(t + 1, n, x);
}
}
主要是解决边界问题以及如何实现边界问题的回溯