一个分酒问题的求解

 

一个分酒问题的求解

流浪狗 2005.3


 这是大部分人都玩过的智力问题,有两个容量为8斤的酒瓶装满酒,另有一个三斤的空酒瓶,利用这三个没有标度的酒瓶将这些酒分给四个人喝,每个人分4斤酒。可能我们也很早的时候就已经得出了正确答案了!可是,我要说的是这的却是一个很有意思的问题,尤其是今天我想用计算机去实现这个问题的解时。可能,接触过游戏的人工智能编程的朋友对这种问题一定不会陌生,因为这跟比如说象棋或者是五子棋里求解一步最佳走法的问题很相似,可以说,你熟悉了这种求解方法,那么要写出一个简单的游戏搜索引擎就不会难道你了。同时,这种问题在面试或者是公务员考试时也会看见他的影子,这就是我提出这个问题的原因!

  现在我们先缝隙一下这个问题该如何实现,首先,我们将他抽象成7个三种类型的瓶子,这样就可以很直观的叠代求解。对于每一时刻的每一状态,都可能出现好几种的倒酒方法,我们从其中选出一种,然后继续构造倒酒方法,直到找到可以倒完酒的方法,如果再找不出可行的到酒方法的情况下,将问题回溯,进行下一个方法的继续求解!下边是倒酒算法的核心函数:


//倒酒的核心函数,产生一个正确的倒酒序列
int GenerateResult(int Box[7],list<MOVE>& list_r)
{
  if(!IsEnd(Box))
  {
    list<MOVE> list_temp;
    int i;
    i=GenAllCurrentMove(Box,list_temp);//产生当前局面的所有走法
    if(i==0)//如果没有走法返回上一层;
    {
      return 0;
    }
    //找出当前局面的所有走法是否能产生正确的倒酒序列
    list<MOVE>::iterator iter=list_temp.begin();
    for(iter;iter!=list_temp.end();iter++)
    {
      list_r.push_back(*iter);
      MakeMove(Box,*iter);
      int res;
      res=GenerateResult(Box,list_r);
      //当前情况下产生以后的所有倒酒方法都不成功,则继续下一个寻求
      if(res==0)
      {
         UnMakeMove(Box,*iter);
         //删除刚才插入的走发
         list_r.pop_back();
      }
      //如果在当前情况下找到解
      else
        return 1;
    }
    //如果当前层没有解,返回上层
     return 0;
  }
  //如果结束
  else
    return 1;
     
}

对于没一个局面的所有走法的求解,可以列举所有的可行的走法:

//产生当前局面所有的倒酒方法,保存在list_temp中
int GenAllCurrentMove(int Box[7],list<MOVE>& list_temp)
{
   int count=0;//倒酒方法的数量
   int max[7]={8,8,3,4,4,4,4};//每个酒瓶或者人的最大容量
   for(int k=0;k<3;k++)
   {
     //当前酒瓶空,继续下一个酒瓶
     if(Box[k]==0)
       continue;
     //循环从左到后找出可以倒酒的方法
     for(int det=0;det<7;det++)
     {
          //如果目标瓶和原瓶相同,继续下一个
          if(det==k)
            continue;
          //瓶之间互相到
          if(det<3)
          {
             //由于0瓶和1瓶是同性质的,所以当一个瓶为空时,不能将另一个瓶里的酒往里面到
             //目标不满
             if(((k==2||det==2)?1:Box[det]>0)&&Box[det]<max[det])
             {
               MOVE pmove;
               //如果目标和原瓶的酒的数量相加大于目标最大的数量
               if(Box[k]+Box[det]>max[det])
               {
                  pmove.Value=max[det]-Box[det];
               }
               //否则
               else
                 pmove.Value=Box[k];
               pmove.From=k;
               pmove.To=det;
               if(IsValid(pmove))
               {
                 list_temp.push_back(pmove);
                  count++;
               }
              }
           }
          //如果直接给人喝的话
          if(det>=3)
          {
                //如果当前这个人还未分够4斤酒
                if(Box[det]+Box[k]<=4)
                {
                   MOVE pmove;
                   pmove.From=k;
                   pmove.To=det;
                   pmove.Value=Box[k];

                   list_temp.push_back(pmove);
                   count++;
                   //从左到右的进行倒酒,碰到当前还一点都未分到酒的人时,此次倒酒结束
                   if(Box[det]==0)
                    break;
                }//if
           }//if
        }
    }
  return count;
}
当然,倒酒的时候,得判断当前到法是否可行,如果来回倒酒或则是倒的时候出现以前倒过的局面,则应该去掉当前的倒酒方法:

//判断当前的走发是否将产生死循环或者将产生一个重复的倒后局面
int IsValid(MOVE& move)
{
  MOVE lastmove;//上一次的倒酒方法
  int temp[7]={8,8,0,0,0,0,0};
  //按倒酒方法倒酒
  MakeMove(Boxes,move);
  list<MOVE>::iterator iter=list_rslt.begin();
  //判断此次倒酒是否将产生与以前重复的局面,从而将次方法剪枝掉
  //如以前出现5 8 3,倒酒后产生5 8 3或者8 5 3
  for(iter;iter!=list_rslt.end();iter++)
  {
    MakeMove(temp,*iter);
    //如果产生以前已经产生的局面
    if((temp[0]==Boxes[0]&&temp[1]==Boxes[1]&&temp[2]==Boxes[2])||
         (temp[1]==Boxes[0]&&temp[0]==Boxes[1]&&temp[2]==Boxes[2]))
    {
      UnMakeMove(Boxes,move);
      return 0;
    }
    lastmove=*iter;
  }

  //恢复倒酒
  UnMakeMove(Boxes,move);

  //如果来回倒酒
  if(lastmove.From==move.To&&lastmove.To==move.From&&lastmove.Value==move.Value)
     return 0;
  return 1;
   
}

//判断是否已经结束
int IsEnd(int Box[7])
{
  //如果三个酒瓶都空了,则表示倒酒结束
  if(Box[0]==0&&Box[1]==0&&Box[2]==0)
      return 1;
  else
      return 0;
}

//按当前的倒酒方法倒酒
void MakeMove(int Box[7],MOVE& move)
{
  Box[move.From]-=move.Value;
  Box[move.To]+=move.Value;
}

//取消上次的倒酒
void UnMakeMove(int Box[7],MOVE& move)
{
  Box[move.From]+=move.Value;
  Box[move.To]-=move.Value;
}

//打印输出
void Print()
{
  int temp[7]={8,8,0,0,0,0,0};
  list<MOVE>::iterator iter=list_rslt.begin();
  cout<<"/*有两个容量为8斤的酒瓶装满酒,另有一个三斤的空酒瓶,"<<"/n"
     <<"利用这三个没有标度的酒瓶将这些酒分给四个人喝,每个人分4斤酒。*//n";
  cout<<"      求解结果/n";
  cout<<"8 8 0    0 0 0 0"<<endl;
  //控制输出条件
  //count1为前一次的所有酒瓶里的酒的和,count2为后一次的
  int  count1,count2;
  count1=16;
  count2=0;
  for(iter;iter!=list_rslt.end();iter++)
  {
    MakeMove(temp,*iter);
    count2=temp[0]+temp[1]+temp[2];
    if(count1!=count2)
    {
      cout<<temp[0]<<" "<<temp[1]<<" "<<temp[2]<<"    "<<temp[3]<<" "<<temp[4]<<" "
         <<temp[5]<<" "<<temp[6]<<endl;
    }
    else
      cout<<temp[0]<<" "<<temp[1]<<" "<<temp[2]<<endl;
    count1=count2;
  }
}

下边是主函数体:

#include<list>


 
struct MOVE{
  int From;//从那儿倒
  int To;//倒去那儿
  int Value;//倒的斤数
  };
 
//产生的走法链表 ,全局变量
list<MOVE> list_rslt;
//放酒的酒瓶Box[0..1]代表装8斤酒的酒瓶,Box[2]代表可以装三斤酒的酒瓶
//Box[3..7]代表喝酒的四个人
int Boxes[7]={8,8,0,0,0,0,0};

//函数声明
int IsEnd(int Box[7]);
int IsValid(MOVE& move);
int GenAllCurrentMove(int Box[7],list<MOVE>& list_temp);
void MakeMove(int Box[7],MOVE& move);
void UnMakeMove(int Box[7],MOVE& move);
int GenerateResult(int Box[7],list<MOVE>& list_r);
void Print();
 
 
void main()
{


  if(GenerateResult(Boxes,list_rslt))
  {
 
    Print();
  }
  else
    cout<<"NO Result!";
}

运行结果如图:

好的,我可以帮你解答这个问题。以下是使用Java的BFS算法来实现三个酒瓶的分酒问题的示例代码: ```java import java.util.*; public class BottleProblem { // 定义酒瓶的容量 private static final int[] CAPACITY = {12, 8, 5}; // 定义酒瓶状态类 private static class State { private int[] bottles; public State(int[] bottles) { this.bottles = bottles; } public int[] getBottles() { return bottles; } public boolean isGoalState() { return bottles[0] == 6 && bottles[1] == 6 && bottles[2] == 0; } public List<State> getNextStates() { List<State> nextStates = new ArrayList<>(); // 尝试所有可能的操作 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i != j) { int[] newBottles = Arrays.copyOf(bottles, 3); int amount = Math.min(newBottles[i], CAPACITY[j] - newBottles[j]); newBottles[i] -= amount; newBottles[j] += amount; nextStates.add(new State(newBottles)); } } } return nextStates; } } // 使用BFS算法求解 public static void solve() { Queue<State> queue = new LinkedList<>(); Set<State> visited = new HashSet<>(); // 初始状态,三个酒瓶都为空 int[] initBottles = {0, 0, 0}; State initialState = new State(initBottles); queue.add(initialState); visited.add(initialState); while (!queue.isEmpty()) { State currentState = queue.poll(); if (currentState.isGoalState()) { System.out.println("找到解决方案!"); System.out.println(Arrays.toString(currentState.getBottles())); return; } List<State> nextStates = currentState.getNextStates(); for (State nextState : nextStates) { if (!visited.contains(nextState)) { queue.add(nextState); visited.add(nextState); } } } System.out.println("未找到解决方案!"); } public static void main(String[] args) { solve(); } } ``` 这段代码使用BFS算法来搜索所有可能的状态,直到找到目标状态(即三个酒瓶中有6升、6升和0升的解决方案)或者搜索完所有可能的状态。在每个状态中,我们尝试所有可能的操作,即从一个酒瓶向另一个酒瓶倒酒,直到酒瓶中的酒量达到容量限制或者另一个酒瓶已满为止。 希望这个示例代码能够帮助到你!如果有任何问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值