一、分支界限算法
分支限界法类似于回溯法,是一种在问题的解空间树上搜索问题解的算法。
分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
二、分支界限算法策略
分支限界法常以广度优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
三、界限函数
利用约束函数和限界函数剪去无效的分支,提高搜索效率。
求解最小值问题时:
维护一个活节点表,从活结点表中选择一个结对作为扩展节点
对扩展节点的每个分支,计算器下界值Bound(i).
如果当前最大目标函数值bestc<=Bound(i),那么结点 i 就不会放入活结点表中个,否则放入。从而完成剪枝操作。
【例】一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:
阵列
4 10
0234500067
1034560500
2045600671
0000000089
有4个细胞。
【算法分析】
⑴从文件中读入m*n矩阵阵列,将其转换为boolean矩阵存入bz数组中;
⑵沿bz数组矩阵从上到下,从左到右,找到遇到的第一个细胞;
⑶将细胞的位置入队h,并沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;
⑷将h队的队头出队,沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;
⑸重复4,直至h队空为止,则此时找出了一个细胞;
⑹重复2,直至矩阵找不到细胞;
⑺输出找到的细胞数。
代码实现
#include<cstdio>
using namespace std;
int dx[4]={-1,0,1,0}, // x,y 方向上的增量
dy[4]={0,1,0,-1};
int bz[100][100],num=0,n,m; //二维数组,存储原始矩阵
void doit(int p,int q){ //p,q矩阵的行列号
int x,y,t,w,i;
int h[1000][2]; //顺序队列,记录入队细胞元素在二维数组中的位置
num++; //细胞个数增1
bz[p][q]=0; //细胞元素清0
t=0;w=1; //队列指针。t队首,w 队尾
h[1][1]=p; h[1][2]=q; //遇到的第一个细胞入队
do {
t++; //队头指针加1
for (i=0;i<=3;i++){ //沿细胞的上下左右四个方向搜索细胞
x=h[t][1]+dx[i];
y=h[t][2]+dy[i];
if ((x>=0)&&(x<m)&&(y>=0)&&(y<n)&&(bz[x][y])){
w++;
h[w][1]=x; h[w][2]=y; bz[x][y]=0;
} //本方向搜索到细胞就入队
}
}while (t<w); //直至队空为止
}
int main(){
int i,j;
char s[100],ch;
scanf("%d%d\n",&m,&n);
for (i=0; i<=m-1;i++ )
for (j=0;j<=n-1;j++ )
bz[i][j]=1; //初始化
for (i=0;i<=m-1;i++) {
gets(s);
for (j=0;j<=n-1;j++) if (s[j]=='0') bz[i][j]=0;
}
for (i=0;i<=m-1;i++)
for (j=0;j<=n-1;j++)
if (bz[i][j]) doit(i,j); //在矩阵中寻找细胞
printf("NUMBER of cells=%d",num);
return 0;
}
四、迷宫问题
如下图所示,给出一个N*M的迷宫图和一个入口、一个出口。
编一个程序,打印一条从迷宫入口到出口的路径。这里黑色方块的单元表示走不通(用-1表示),白色方块的单元表示可以走(用0表示)。只能往上、下、左、右四个方向走。如果无路则输出“no way.”。
【算法分析】
只要输出一条路径即可,所以是一个经典的回溯算法问题,本例给出了回溯(深搜)程序和广搜程序。实现见参考程序。
代码实现;
深搜:
#include <iostream>
using namespace std;
int n,m,desx,desy,soux,souy,totstep,a[51],b[51],map[51][51];
bool f;
int move(int x, int y,int step){
map[x][y]=step; //走一步,作标记,把步数记下来
a[step]=x; b[step]=y; //记路径
if ((x==desx)&&(y==desy)) {
f=1; totstep=step;
}
else {
if ((y!=m)&&(map[x][y+1]==0)) move(x,y+1,step+1); //向右
if ((!f)&&(x!=n)&&(map[x+1][y]==0)) move(x+1,y,step+1); //往下
if ((!f)&&(y!=1)&&(map[x][y-1]==0)) move(x,y-1,step+1); //往左
if ((!f)&&(x!=1)&&(map[x-1][y]==0)) move(x-1,y,step+1); //往上
}
}
int main(){
int i,j;
cin>>n>>m; //n行m列的迷宫
for (i=1;i<=n;i++) //读入迷宫,0表示通,-1表示不通
for (j=1;j<=m;j++)
cin>>map[i][j];
cout<<"input the enter:";
cin>>soux>>souy; //入口
cout<<"input the exit:";
cin>>desx>>desy; //出口
f=0; //f=0表示无解;f=1表示找到了一个解
move(soux,souy,1);
if (f) {
for (i=1;i<=totstep;i++) //输出直迷宫的路径
cout<<a[i]<<","<<b[i]<<endl;
}
else cout<<"no way."<<endl;
return 0;
}
广搜:
#include <iostream>
using namespace std;
int u[5]={0,0,1,0,-1},
w[5]={0,1,0,-1,0};
int n,m,i,j,desx,desy,soux,souy,head,tail,x,y,a[51],b[51],pre[51],map[51][51];
bool f;
int print(int d){
if (pre[d]!=0) print (pre[d]); //递归输出路径
cout<<a[d]<<","<<b[d]<<endl;
}
int main(){
int i,j;
cin>>n>>m; //n行m列的迷宫
for (i=1;i<=n;i++) //读入迷宫,0表示通,-1表示不通
for (j=1;j<=m;j++)
cin>>map[i][j];
cout<<"input the enter:";
cin>>soux>>souy; //入口
cout<<"input the exit:";
cin>>desx>>desy; //出口
head=0;
tail=1;
f=0;
map[soux][souy]=-1;
a[tail]=soux; b[tail]=souy; pre[tail]=0;
while (head!=tail) { //队列不为空
head++;
for (i=1;i<=4;i++) { //4个方向
x=a[head]+u[i]; y=b[head]+w[i];
if ((x>0)&&(x<=n)&&(y>0)&&(y<=m)&&(map[x][y]==0)) { //本方向上可以走
tail++;
a[tail]=x; b[tail]=y; pre[tail]=head;
map[x][y]=-1;
if ((x==desx)&&(y==desy)) { //扩展出的结点为目标结点
f=1;
print(tail);
break;
}
}
}
if (f) break;
}
if (!f) cout<<"no way."<<endl;
return 0;
}