回溯法(back track method)也称为试探法,是蛮力法的改进。在包含问题的所有可能解空间中,从根节点处罚,按照深度优先策略进行搜索,对于解空间树的某个节点,如果该节点满足问题的约束条件,则进入该子树继续进行搜索,否则将以该节点为根节点进行剪枝。回溯法常常可以避免搜索所有可能解,适用于组合较数较大的问题。
采用回溯法解决0/1背包问题。
【问题描述】:
例如,对于n=3的0/1背包问题,三个物品的重量为{20, 15, 10},价值为{20, 30, 25},背包容量为25,从图8.2所示的解空间树的根结点开始搜索,搜索过程如下。
从物品一号开始,分为两种情况,如果将物品一放入到背包中,或者将物品一不放入背包中,于是节点1的两个分支 2 和9 , 分支2表示将物品一放到背包中,从节点1到达节点9表示将物品一不放入背包中。同理,依次类推。
这样我们就可以用树的形式展示出各种路径,再分别计算每种方案价值,从而得到最优解。
【C代码】:
int BackTrack(int i)
{
if(i>n-1){
if(bestP<cp){
for (int k=0;k<n;k++) X[k]=cx[k];//存储最优路径
bestP=cp;
}
return bestP;
}
if(cw+a[i].w<=C){ //进入左子树
cw=cw+a[i].w;
cp=cp+a[i].p;
cx[a[i].sign]=1; //装入背包
BackTrack(i+1);
cw=cw-a[i].w;
cp=cp-a[i].p; //回溯,进入右子树
}
cx[a[i].sign]=0; //不装入背包
BackTrack(i+1);
return bestP;
}
int KnapSack3(int n,goods a[],int C,int x[])
{
for(int i=0;i<n;i++)
{
x[i]=0;
a[i].sign=i;
}
sort(a,a+n,m);//将各物品按单位重量价值降序排列
BackTrack(0);
return bestP;
}
利用回溯法解决图的找色问题。
【问题描述】:
给定无向连通图G=(V, E)和正整数m,求最小的整数m,使得用m种颜色对G中的顶点着色,使得任意两个相邻顶点着色不同。
【步骤】:
回溯法求解图的着色问题,首先把所有的定点颜色初始化为0,然后依次为每个顶点着色。在图着色对应的解空间树中,如果从根节点到当前节点对应一个部分解,也就是颜色指派都没有冲突,则在当前节点对应一个部分解,也就是所有的颜色指派都没有冲突,则在当前节点处选择第一颗子树继续搜索,也就是为下一个顶点着色1,否则,对当前子树的兄弟子树继续搜索,也就是为当前顶点着下一个颜色如果所有m种颜色都已尝试过且都发生冲突,则回溯到当前节点的父节点处,上一个顶点的颜色被改变,依次类推。
【例题】:
如下无向图所示,为A,B,C,D,E,分别给每个点着色,着三种颜色 1,2,3。并且相邻的颜色不相同。请问A,B,C,D,E分别着哪种颜色?
利用回溯方法求解解空间,解空间树如下图所示:
【解题说明】:
节点1到2我们给顶点A着色 第一种颜色,从节点2开始我们给无向图的B点着色,节点2到节点3我们给B着色1;当我们给B着色1,出现错误,因为A和B相邻,A已经着色1了,所以B不能再着色1。于是我们回溯到根节点,节点2,增加新的分支,节点2到节点4我们给B着色2,此时不会出现任何的冲突。
我们继续给无向图C点着色,节点4到节点5表示给C着色1,出现冲突回溯到根节点4;节点4到节点6,表示给C着色2,同样会出现冲突,回溯到根节点;节点4到节点7表示给C着色3,不会出现冲突,这样就可以接着给无向图D点着色。
给D着色1,不会出现冲突,这样接着给E着色。因为D已经着色1了,再给E着色1出现冲突;给E着色2出现冲突;给E着色3出现冲突;树中的节点8到11 12 13 分别表示这三种情况。都会出现冲突,这样回溯到根节点7。
根节点7到10表示给D着色3,不会产生冲突。继续给E着色,给E着色1,进行验证,不产生冲突,这样我们就找到了一个合适的解。
图中剪枝的部分为黄色节点的部分,是产生冲突的解。所以最终无向图点ABCDE点的解围为{1,2,3,3,1}。
【C代码部分】:
void GraphColor(int n, int c[ ][ ], int m)
//所有数组下标从1开始
{
for (i=1; i<=n; i++ ) //将数组color[n]初始化为0
color[i]=0;
k=1;
while (k>=1)
{
color[k]=color[k]+1;
while (color[k]<=m)
if Ok(k) break;
else color[k]=color[k]+1; //搜索下一个颜色
if (color[k]<=m && k= =n) //求解完毕,输出解
{
for (i=1; i<=n; i++)
cout<<color[i];
return;
}
else if (color[k]<=m && k<n)
k=k+1; //处理下一个顶点
else {
color[k]=0;
k=k-1; //回溯
}
}
}
bool Ok(int k) //判断顶点k的着色是否发生冲突
{
for (i=1; i<k; i++)
if (c[k][i]= =1 && color[i]= =color[k])
return false;
return true;
}
本节完毕,下一节分支限界算法。