回溯法是一种系统的搜索问题解的算法。
回溯法的一般执行步骤如下:
1 、针对问题,定义一个解空间,它包含问题的解。
2 、用适于搜索的方式组织该空间。
3 、以深度优先的方式搜索该空间,并利用限界函数来避免搜索不可能产生解的子空间。
回溯法通常在解空间树上进行搜索,而解空间树通常有子集树和排列树。
1 、当所给的问题是从 n 个元素的集合中找出满足某种性质的子集时,相应的解空间树称为子集树。例如、子集问题和子集和问题。
子集树的回溯框架:
void backtrack(int t)
{
if (t>=n)
output(x);
else
{
x[t] = 0;
if (constraint0(t) && bound0(t))
backtrack(t+1);
x[t] = 1;
if (constraint1(t) && bound1(t))
backtrack(t+1);
}
}
★ 子集问题:求某个集合的所有子集。
#include
using namespace std;
const int N = 4;
int x[N] = {2, 8, 12, 6};
int isUsed[N];
void print()
{
for (int i=0; i
if (isUsed[i])
cout<
cout<
}
void subSet(int t)
{
if (t >= N)
print();
else
{
isUsed[t] = 0;
subSet(t+1); // 此问题没有限界函数
isUsed[t] = 1;
subSet(t+1); // 此问题没有限界函数
}
}
int main()
{
subSet(0);
return 0;
}
★ 子集和问题:求某个集合的所有子集,使得该子集满足其和等于给定的某个值。
#include
using namespace std;
const int N = 4;
int x[N] = {2, 8, 12, -6};
int isUsed[N];
int sum = 6;
int temp = 0;
void print()
{
for (int i=0; i
if (isUsed[i])
cout<
cout<
}
void subSetSum(int t)
{
if (t >= N)
{
if (sum == temp)
print();
}
else
{
isUsed[t] = 1;
if (temp != sum)
{
temp += x[t]; // 表示 x[t] 被添加到子集中去
subSetSum(t+1);
temp -= x[t]; // 把 x[t] 从子集中取出
}
isUsed[t] = 0;
subSetSum(t+1);
}
}
int main()
{
subSetSum(0);
return 0;
}
2 、当所给的问题是从 n 个元素的集合中找出满足某种性质的排列时,相应的解空间树称为排列树。例如、排列问题和 8 皇后问题。
排列树的两种回溯框架:
void backtrack1(int t)
{
if (t>=n)
output(x);
else
for (int i=t ; i
{
swap(x[t], x[i]);
if (constraint(t) && bound(t))
backtrack(t+1);
swap(x[t],x[i]);
}
}
void backtrack2(int t)
{
if (t>=n)
output(x);
else
for (int i=0 ; i
{
x[t] = i;
if (constraint(t) && bound(t))
backtrack(t+1);
}
}
★ 排列问题:求某个集合的全排列。
框架 1 :
#include
using namespace std;
const int N = 4;
int x[N] = {3,5,9,6};
void print()
{
for (int i=0; i
cout << x[i] << " " ;
cout << endl;
}
void swap(int *x,int i,int j)
{
int temp;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
void backtrack(int t)
{
if (t >= N)
print();
for (int i=t; i
{
swap(x, t, i);
backtrack(t+1);
swap(x, t, i);
}
}
int main()
{
backtrack(0);
return 0;
}
框架 2 :
#include
using namespace std;
const int N = 4;
int x[N] = {3,5,9,6};
int t[N]; // 表示位置
void print()
{
for (int i=0; i
cout << x[t[i]] << " " ;
cout << endl;
}
bool isEqual(int k)
{
for (int i=0; i<=k-1; ++i)
for (int j=i+1;j<=k;++j)
if (t[i] == t[j])
return false ;
return true ;
}
void swap(int *x,int i,int j)
{
int temp;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
void backtrack(int i)
{
if (i >= N)
print();
for (int j=0; j
{
t[i] = j;
if (isEqual(i)) // 不能有重复
backtrack(i+1);
}
}
int main()
{
backtrack(0);
return 0;
}
★ 8 皇 后问题:在8X8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
#include
#include
using namespace std;
const int N = 8;
int x[N] = {0,1,2,3,4,5,6,7};
void print()
{
for (int i=0; i
cout<
cout<
}
bool isPlace(int k)
{
for (int i=0; i
{
if (x[k]==x[i] || abs(x[k]-x[i])==abs(k-i))
return false ;
}
return true ;
}
void swap(int *x,int i,int j)
{
int temp;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
void nQueen(int t)
{
if (t >= N)
print();
else
{
// 框架 1
for (int i=t; i
{
swap(x, i, t);
if (isPlace(t))
nQueen(t+1);
swap(x, i, t);
}
/* // 框架 2
for (int i=0; i
{
x[t]=i;
if (isPlace(t))
nQueen(t+1);
}
*/
}
}
int main()
{
nQueen(0);
return 0;
}
回溯法是一种比较有效的搜索算法,读者要体会其中的精要,框架只供理解,遇到具体问题一定要具体分析,不要一味的套用框架,这样才能以不变应百变。
推荐网站:好巴鹿(http:// www.haobalu.com )