用宽度优先搜索(BFS)解决分油问题

选了李翠华老师的计算智能课程,第一次作业是解决一个小孩分油问题,把实验报告和代码上传,留底。

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]);
}

简单测试:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值