(1)回溯算法的简述
· 本质是一种穷举算法:对于有些最优解问题,没有任何的理论也无法采用精确的数学公式来帮助我们找到最优解,我们只 能采用穷举算法。回溯技术就是一种系统化的穷举搜索技术。
· 所谓回溯技术就是像人走迷宫一样,先选择一个前进方向尝试,一步步试探,遇到死胡同时不能再前进的时候就退到上一 个分支点,另选一个方向尝试,同时在前进和回撤的路上都设置一些标记,以便能够正确返回,直到达成目的或所有的可 行方案都已经尝试完为止。
(2)回溯算法的实现
· 在通常情况下,使用递归方式来实现回溯技术,也就是在每一个分岔点进行递归尝试。回溯通常采用栈来记录回溯过程, 使用栈可以使穷举过程能回溯到所要的位置,并继续在指定层次上往下穷举所有可能的解。
· 回溯法在问题的解空间树中,按深度优先策略,从根节点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断 该节点是否包含问题得解,如果肯定不包含,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该 子树,继续按深度优先策略搜索。
(3)生成问题状态的基本方法
· 深度优先的问题状态生成法:如果对于一个扩展节点R,一旦产生了它的一个儿子C,就把C当作新的扩展节点。在完成对 子树C的穷尽搜索之后,将R重新变成扩展节点,继续生成R的下一个儿子(如果存在)。
· 为了避免生成不可能产生的最佳解的问题状态,要不断利用那些限界函数来处死那些实际上不可能产生所需解的活结点, 以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。
(4)回溯法的基本思想
· 针对所给问题,定义问题的解空间;
· 确定易于搜索的解空间结构;
· 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
用伪代码描述如下:
• Proc search(当前状态);
• Begin
• If 当前状态等于目标状态 then exit;
• for 对所有可能的新状态
• search(新状态);
• End;
例一:背包问题
假设有一个能装入总体积为S的背包和n件体积分别为w1 , w2 , … , wn 的物品,能否从n件物品中挑选若干m件恰好装满背 包,即使w1 +w2 + … + wm=S,如果存在符合上述要求的选择,则称该背包问题有解,否则称其无解,试用递归方法设 计求解背包问题的算法。例如w={1,2,4,8,16,32},s=51, n=6,则解为(1,2,16,32)。
分析:首先说明一下,背包问题有好几种,0-1背包是不能拆分货物的情况下计算容量为s的背包里面最多能装多少货物,此时要 用到动态规划法,还有有的背包问题是能拆分货物的情况下装满的最大价值是多少,对于这个题,它是求是否能填满背 包。那么遍历所有的情况,看是否有满足条件的解即可。
解答:
例二:0-1背包问题
在0 / 1背包问题中,需对容量为c 的背包进行装载。从n 个物品中选取装入背包的物品,每件物品i 的重量为wi ,价值为pi 对于可行的背包装载,背包中物品的总重量不能超过背包的容量,最佳装载是指所装入的物品价值最高。
解答:
void search(int m)
{
if(m>=n)
checkmax(); //检查当前解是否是可行解,若是则把它的价值与max比较
else
{
a[m]=0; //不选第m件物品
search(m+1); //递归搜索下一件物品
a[m]=1; //选第m件物品
search(m+1); //递归搜索下一件物品
}
}
void checkmax()
{
int i, weight=0, value=0;
for(i=0;i<n;i++)
{
if(a[i]==1) //如果选取了该物品
{
weight = weight + w[i]; //累加重量
value = value + v[i]; //累加价值
}
}
if(weight<=c) //若为可行解
if(value>max) //且价值大于max
max=value; //替换max
}
int main()
{
readdata();
search();
printsult();
}
例三:穷举n位二进制数
输入一个小于20的正整数n,要求按从小到大的顺序输出所有的n位二进制数.
• 输入样例
• 3
• 输出样例
• 000 001 010 011 100 101 110 111
解答:
void merge(int m)
{
if(m>=n)
print();
else
{
a[m]=0;
merge(m+1);
a[m]=1;
merge(m+1);
}
}
void print()
{
for(i=0;i<n;i++)
printf("%d",a[i]);
printf(" ");
}
int main()
{
readdata();
merge(0);
}
例四:堡垒问题
如图城堡是一个4×4的方格,为了保卫城堡,现需要在某些格子里修建一些堡垒。城堡中的某些格子是墙,其余格子都是 空格,堡垒只能建在空格里,每个堡垒都可以向上下左右四个方向射击,如果两个堡垒在同一行或同一列,且中间没有墙 相隔,则两个堡垒都会把对方打掉。问对于给定的一种状态,最多能够修建几个堡垒。
分析:还是使用回溯法的模板,为了简答起见,可以用一维数组存数据,用除四取整和取余得到行数和列数,每次检验先放或者 能放就放。
解答:
oid search(int m)
{
if(m>=16)
checkmax();
else
{
a[m]=0;
search(m+1);
if(canplace(m))
{
a[m]=1;
search(m+1);
}
}
}
void checkmax()
{
int i,value=0;
for(i=0;i<16;i++)
{
if(a[i]==1)
value=value+1;
}
if(value>max)
max=value; //替换max
}
int canplace(int m)
{
int i,j,row,col;
row=m/4;
col=m%4;
if(map[row][col]!=0)//如果这里不能放
return 0;
else
{
for(i=row;i>=0;i--)
{//从当前行沿着同一列返回检查
if(map[i][col]==2) //若先遇到已经放置了堡垒
return 0;
else if(map[i][col]==1) //若是墙
break;
}
for(j=col;j>=0;j--)
{
if(map[row][j]==2)
return 0;
else if(map[row][j]==1)
break;
}
return 1;
}
}
int main()
{
readdata(); //读入数据
search(0); //递归搜索
printresult();
}
例五:装载问题
有两艘船,载重量分别是c1、 c2,n个集装箱,重量是wi (i=1…n),且所有集装箱的总重量不超过c1+c2。确定是否有可 能将所有集装箱全部装入两艘船。
分析:先计算一条船能装的最大重量的货物,计算剩下的货物是否能被第二条船装下即可。
解答:
void search(int m)
{
if(m>n)
checkmax();
else
{
a[m]=0;
search(m+1);
if(cw+w[m]<=c1)
{
a[m]=1;
cw=cw+w[m];
search(m+1);
}
}
int main()
{
readdata(); //读入数据
search(0); //递归搜索
printresult();
}
例六:迷宫问题
给一个20×20的迷宫、起点坐标和终点坐标,问从起点是否能到达终点。
分析:解释一些东西。首先我用了flag,是因为本来想有结果就结束所有循环,返回结果说有解就行,但是return只能返回上一 层,不能结束整套search,只能用一个flag标志,初值赋为0,一旦有解,改变值为1,无解则从头到尾不管它,最后根据 它的值来判断是否有解。第二是对于这种很经典的回溯法解决的问题,主要根据它下步执行的多少来判断分支的多少。比 如前边的装载和背包,它们要么装要么不装,不装就是不装,装的时候就用剪枝函数判断是否能装,能装就装下。这个题 每个分支有四种选择,左右上下四个方向可以转向,但需要设定一个used函数,来记录哪个位置是否走过,这里就跟分支 限界法有一点相似了。其次就是你怎么处理输入,可以用 . 和 X,也可以转化成0和1。其次有的时候会记录点在一维数组 里,通过除以20取整取余来得到行数和列数,这个都是可以的。
解答:
int used[21][21],a[21][21];
int t1,t2;
int flag=0;
int canplace(int row, int col)
{
if(row>=0&&row<20&&col>=0&&col<20&&a[row][col]==0&&used[row][col]==0)
return 1;
else
return 0;
}
void search(int row, int col)
{
if(row==t1&&col==t2)//若已经到达
{
flag=1;
printsuit();
}
int r,c;
a[row][col]=1;
r=row; //左
c=col-1;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row+1; //下
c=col;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row; //右
c=col+1;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
r=row-1; //上
c=col;
if(canplace(r,c)) //判断(r,c)位置是否已经走过
search(r,c); //递归搜索(r,c)
}
例七:素数环
例八:八皇后问题
例九:图的m着色问题
例十:最大团问题
例十一:翻硬币问题
例十二:旅行售货员问题
例十三:批处理作业调度