先放上算法框架:
递归回溯法算法框架[一]
int Search(int k)
{
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果
if (到目的地) 输出解;
else Search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}
递归回溯法算法框架[二]
int Search(int k)
{
if (到目的地) 输出解;
else
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果;
Search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}
例题:
【例1】素数环:从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。
【算法分析】
非常明显,这是一道回溯的题目。从1开始,每个空位有20种可能(即所谓的算符种数),只要填进去的数合法:与前面的数不相同;与左边相邻的数的和是一个素数(即满足的条件)。第20个数(即目的地)还要判断和第1个数的和是否素数(可以放在输出里做)。
【例3】任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。当n=7共14种拆分方法
#include<cstdio>
#include<iostream>
#include<cstdlib>
using namespace std;
int a[10001]={1},n,total;
int search(int,int);
int print(int);
int main()
{
cin>>n;
search(n,1);
//将要拆分的数n传递给s
cout<<"total="<<total<<endl; //输出拆分的方案数
system("pause");
}
int search(int s,int t)
{
int i;
for (i=a[t-1];i<=s;i++)
if (i<n)
//当前数i要大于等于前1位数,且不过n
{
a[t]=i;
//保存当前拆分的数i
s-=i;
//s减去数i, s的值将继续拆分
if (s==0) print(t);
//当s=0时,拆分结束输出结果
else search(s,t+1);
//当s>0时,继续递归
s+=i;
//回溯:加上拆分的数,以便产分所有可能的拆分
}
}
int print(int t)
{
cout<<n<<"=";
for (int i=1;i<=t-1;i++)
//输出一种拆分方案
cout<<a[i]<<"+";
cout<<a[t]<<endl;
total++;
//方案数累加1
}
【例4】八皇后问题:要在国际象棋棋盘中放八个皇后,使任意两个皇后都不能互相吃。(提示:皇后能吃同一行、同一列、同一对角线的任意棋子。)
放置第i个(行)皇后的算法为:
int search(i);
{
int j;
for (第i个皇后的位置j=1;j<=8;j++ ) //在本行的8列中去试
if (本行本列允许放置皇后)
{
放置第i个皇后;
对放置皇后的位置进行标记;
if (i==8) 输出 //已经放完个皇后
else search(i+1); //放置第i+1个皇后
对放置皇后的位置释放标记,尝试下一个位置是否可行;
}
}
【算法分析】
显然问题的关键在于如何判定某个皇后所在的行、列、斜线上是否有别的皇后;可以从矩阵的特点上找到规律,如果在同一行,则行号相同;如果在同一列上,则列号相同;如果同在/ 斜线上的行列值之和相同;如果同在\ 斜线上的行列值之差相同;从下图可验证:
考虑每行有且仅有一个皇后,设一维数组A[1..8]表示皇后的放置:第i行皇后放在第j列,用A[i]=j来表示,即下标是行数,内容是列数。例如:A[3]=5就表示第3个皇后在第3行第5列上。
经过对例题和上机练习的研究,我发现这就是传说中的:暴搜。。。
值得一提的是排列除了一个一个放的另一种方法:
先读入整个序列,对于这个序列,要做的就是确定第一个放什么,轮流拿这个序列中的每个元素放进来,然后再对除了第一个以外的序列执行同样操作。
如果有重复元素,在放每一个元素之前,要判断与这个元素相同的元素之前有没有用过。放代码(允许重复元素):