选了李翠华老师的计算智能课程,第一次作业是解决一个小孩分油问题,把实验报告和代码上传,留底。
1.问题描述
编写一个程序,解决下列问题:
小孩分油问题:两个小孩去打油,一人带了一个一斤的空瓶,另一个带了一个七两、一个三两的空瓶。原计划各打一斤油,可是由于所带的钱不够,只好两人合打了一斤油,在回家的路上,两人想平分这一斤油,可是又没有其它工具。试仅用三个瓶子(一斤、七两、三两)精确地分出两个半斤油来。
2.算法设计
数据结构:
vector<int> b; /*一个vector中有三个数,分别表示一斤瓶、七两瓶和三两瓶的油量*/
bool vis[10010]; /*记录某状态是否被遍历过,一个状态用b[0]*100+b[1]*10+b[2]表示*/
int cap[3] = {10,7,3}; /*记录每个瓶子的容量*/
queue<vector<int> > Q; /*BFS中使用的队列*/
struct Path{ /*路径,pre表示前一个节点,x表示当前状态*/
int pre;
vector<int> x;
}
由题意分析这可以用搜索做,一共有三个瓶子,每个瓶子可以向其他两个瓶子倒油,或者保持自己当前的状态,也就是每次轮到瓶子x,它有三条路可以走,所以这时用宽度优先搜索(BFS),每次搜索三个状态,当目前状态为(5,5,0)时退出,这时我们搜到的第一结果必定是最短路,也就是倒油次数最少的方法。这边需要注意的是我们需要输出倒油的顺序,也就是需要记录宽度优先搜索的路径。其A->B的状态转移过程可由下图表示:
其中,A、B分别表示瓶子A、B中目前的油量,cap(B)表示瓶子B的容量,cap为capacity的缩写。
由上图1可知,虽然我们有三种瓶子,但只需要一个函数Transform(vector b, int f, int t)就能表示所有的转移,f(from)表示倒出油的瓶子,t(to)表示倒入油的瓶子。
由于A->B后,B也可以向A转移,为了避免出现死循环,这里我使用了记忆化搜索,对于每次倒油,维护一个布尔数组vis,记录这个状态是否被遍历过,若是遍历过,下次经过时就直接跳过。当遇到状态(5,5,0)时退出搜索,并输出路径以及所用的步数。
3.程序流程
1、用{10,0,0}初始化vector b;
2、调用BFS函数;
3、将b存入队列Q中,使用while循环判断Q是否为空,若为空则退出;
4、若不为空,则将Q的第一个节点取出,f++,f表示当前节点有多少个,记录路径用;
5、判断当前节点是否为(5,5,0),若是则将记录当前节点f,并回到main函数,否则转到6;
6、使用for循环,分别对vector中的三个节点使用Transform函数,若是Transform函数出来的结果数组是已经被遍历过,则不处理,若没遍历过,就将这个状态记录到路径中,并将该节点存入Q中;
7、当回到main函数后,调用Output函数来输出路径,由于我们找到的节点f是最后一个节点,所以我们需要先回溯到第一个节点,再输出。递归Output(path[f].pre)找到pre为0的点,然后再输出就可以了。
4.核心伪代码
vector<int> Transform(vector<int> b,int f,int t){ //状态转移函数
if(b[f] > cap[t]-b[t]){
b[f] -= (cap[t]-b[t]);
b[t] = cap[t];
}
else{
b[t] += b[f];
b[f] = 0;
}
return b;
}
void bfs(vector<int> b){ //BFS,宽度优先搜索函数
bool vis[20010];
memset(vis,false,sizeof vis);
vis[b[0]*100 + b[1]*10 + b[2]] = true;
int f = -1,cnt = 1;
path[0].pre = -1;
path[0].x = b;
queue<vector<int> > Q;
Q.push(b);
while(!Q.empty()){
b = Q.front();
f++;
if(b[0] == 5&&b[1] == 5){
res = f;
return ;
}
Q.pop();
for(int i = 0;i < 3;i++){
if(b[i] != 0){
for(int j = 1; j < 3; j++){
vector<int> a;
a = b;
a = Transform(a,i,(i + j)%3);
if(!vis[a[0]*100 + a[1]*10 + a[2]]){
Q.push(a);
vis[a[0]*100 + a[1]*10 + a[2]] = true;
path[cnt].pre = f;
path[cnt].x = a;
cnt++;
}
else{
continue;
}
}
}
}
}
return ;
}
void Output(int i){ //输出函数
if(i == -1) return ;
Output(path[i].pre);
cot++;
printf("%d %d %d\n",path[i].x[0],path[i].x[1],path[i].x[2]);
}
简单测试: