分油问题
一个一斤的瓶子装满油,另有一个七两和一个三两的空瓶,再没有其它工具。只用这三个瓶子怎样精确地把一斤油分成两个半斤油。
选择广度优先算法来求解分油问题可以得到通过最少步骤完成分油的最优解。
1)定义状态结点
分油过程实际上就是将油从一个油瓶倒入另一个油瓶。分油过程中,各个油瓶中的油在不断变化,因此需要记录各个油瓶在不同状态所装油的多少。这里用一个数组bottle[3]存放当前油瓶中所装油的多少,不同油瓶用数组下标区分,数组元素bottle[0]是一斤油瓶中的油,bottle[1]是七辆油瓶中的油,而bottle[2]是三两油瓶中的油。
此外,结点中用变量last还要记录每个状态是从哪一个状态变化来的,就是扩展出该结点的父结点编号。
2)扩展规则
很明显,油瓶中必须有油才能把油倒出,同样油瓶必须不满才能将油倒入。分油过程中,将油从一个油瓶倒入另一个油瓶,可能的情形用变量i表示,一共只有6种,每种情形的序号与油瓶编号的关系如下表所示:
分油情形 i 0 1 2 3 4 5
倒出油的油瓶 i\2 0 0 1 1 2 2
倒入油的油瓶 (i+3)\2 Mod 3 1 1 2 0 0 1
3)重复结点和目标结点的判断
结点是否相同只需比较油瓶的状态。对于重复结点,需要将队列中的结点逐一检查,目标结点的判断则比较简单。
4)程序代码如下(VC6.0下编译通过):
#include<iostream.h>
#include<iomanip.h>
const maxn=100;
struct tnode{
int bottle[3]; //当前油瓶装的油
int last; //父结点
int souc; //源瓶
int dest; //目标瓶
}state[maxn]; //状态队列
int capacity[3]; //油瓶容量
void init(){ //初始化
state[0].bottle[0]=10;
state[0].bottle[1]=0;
state[0].bottle[2]=0;
state[0].last=0;
state[0].souc=0;
state[0].dest=0;
capacity[0]=10;
capacity[1]=7;
capacity[2]=3;
}
bool expand(tnode& temp,int i,int j){ //扩展结点
if(temp.bottle[i]>0 && capacity[j]>temp.bottle[j]){ //如果源瓶中有油且目标瓶不满
if(temp.bottle[i]>=capacity[j]-temp.bottle[j]){ //源瓶的油大于目标瓶空余容量
temp.bottle[i]=temp.bottle[i]-capacity[j]+temp.bottle[j];//源瓶有余
temp.bottle[j]=capacity[j]; //目标瓶满
}
else{ //否则
temp.bottle[j]=temp.bottle[j]+temp.bottle[i];
temp.bottle[i]=0; //源瓶空
}
temp.souc=i; //记录源瓶于目标瓶
temp.dest=j;
return 1; //可扩展,返回1
}
return 0;
}
bool repeat(tnode state[],tnode temp,int tail){ //重复结点判断
for(int i=0;i<=tail;i++){
for(int j=0;j<3;j++)
if(temp.bottle[j]!=state[i].bottle[j])break;
if(j==3)return 1;
}
return 0;
}
bool finds(tnode temp){ //目标结点判断
if(temp.bottle[1]==5||temp.bottle[0]==5)return 1;
return 0;
}
void printpath(tnode state[],int tail){ //输出路径
if(tail>0){
tail=state[tail].last;
printpath(state,tail);
cout<<setw(3)<<tail<<setw(5)<<state[tail].last<<" ";
cout<<setw(3)<<state[tail].souc<<"-->"<<state[tail].dest<<" ";
for(int i=0;i<3;i++)
cout<<setw(3)<<state[tail].bottle[i];
cout<<endl;
}
}
void bfs(){ //搜索主程序
tnode temp;
int head=0,tail=0,i;
while(head<=tail && tail<maxn){
for(i=0;i<6;i++){
temp=state[head];
if(expand(temp,i/2,(i+3)/2%3)){
if(repeat(state,temp,tail))continue;
state[++tail]=temp;
state[tail].last=head;
if(finds(temp)){ //找到目标结点
tail++; //添加一个结点
state[tail].last=tail-1; //最后结点的父结点是目标结点
printpath(state,tail); //输出搜索路径
return; //找到最优解。退出程序
}
}
}
head++;
}
}
void main()
{
init();
bfs();
}
执行结果为:
0 0 0-->0 10 0 0
1 0 0-->1 3 7 0
4 1 1-->2 3 4 3
6 4 2-->0 6 4 0
8 6 1-->2 6 1 3
10 8 2-->0 9 1 0
12 10 1-->2 9 0 1
14 12 0-->1 2 7 1
16 14 1-->2 2 5 3
因为这里只求最优解,所以在找到解后,就退出程序,如果要求全部解,可将语句goto end换成break,并去掉语句end:;。
链接来自:http://muyuepp.blog.163.com/blog/static/195953512008326101522837/