| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 2648 人关注过本帖
标题:[讨论][转载]经典问题分酒问题
只看楼主 加入收藏
qwer46
Rank: 1
等 级:新手上路
帖 子:11
专家分:0
注 册:2006-3-28
收藏
得分:0 
开始三个容器是8/8/0
a)8/5/3  将3给甲;剩下为8/5/0

b)8/2/3  将2给乙;剩下为8/0/3

c)8/3/0

d)5/3/3

e)5/6/0

f)2/6/3

g)2/8/1  将1给甲;剩下为2/8/0

h)2/5/3

i)0/7/3

j)3/7/0

k)3/4/3

l)6/4/0

m)6/1/3  将1给丙;剩下为6/0/3

n)8/0/1  将1给丁;剩下为8/0/0

o)5/0/3

p)5/3/0

q)2/3/3  将2给乙;3给丙;丁即可

2006-03-28 11:30
qwer46
Rank: 1
等 级:新手上路
帖 子:11
专家分:0
注 册:2006-3-28
收藏
得分:0 
这是大部分人都玩过的智力问题,有两个容量为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!";
}

2006-03-28 11:31
SunShining
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:31
帖 子:2215
专家分:0
注 册:2006-2-17
收藏
得分:0 
~~~~~强!

[glow=255,violet,2]闭关修炼ing...[/glow] [FLASH=360,180]http://www./chinaren.swf[/FLASH]
2006-03-28 12:07
qwer46
Rank: 1
等 级:新手上路
帖 子:11
专家分:0
注 册:2006-3-28
收藏
得分:0 
呵呵.
那个核心函数是我转载的!
我也只是初学者,大家多多指教.
2006-03-28 12:26
Knocker
Rank: 8Rank: 8
等 级:贵宾
威 望:47
帖 子:10454
专家分:603
注 册:2004-6-1
收藏
得分:0 
f)2/6/3

g)2/8/1  将1给甲;剩下为2/8/0
我想知道f->g这步是怎么做到的

九洲方除百尺冰,映秀又遭蛮牛耕。汽笛嘶鸣国旗半,哀伤尽处是重生。     -老K
治国就是治吏。礼义廉耻,国之四维。四维不张,国之不国。   -毛泽东
2006-03-28 12:50
Knocker
Rank: 8Rank: 8
等 级:贵宾
威 望:47
帖 子:10454
专家分:603
注 册:2004-6-1
收藏
得分:0 
噢,倒回去

九洲方除百尺冰,映秀又遭蛮牛耕。汽笛嘶鸣国旗半,哀伤尽处是重生。     -老K
治国就是治吏。礼义廉耻,国之四维。四维不张,国之不国。   -毛泽东
2006-03-28 12:55
qwer46
Rank: 1
等 级:新手上路
帖 子:11
专家分:0
注 册:2006-3-28
收藏
得分:0 
这不很简单嘛!
用三斤的那个容器,把已经有六斤酒的八斤容器加满.那个三斤的容器不就剩一斤了吗!
再把这一斤给甲!
2006-03-28 19:17
剑人
Rank: 1
等 级:新手上路
帖 子:58
专家分:0
注 册:2005-9-21
收藏
得分:0 
好呀,厉害

还有什么原代码

发发

^_^
2006-04-04 21:59
angelfight
Rank: 1
等 级:新手上路
帖 子:10
专家分:0
注 册:2006-4-2
收藏
得分:0 

.....
2/6/3
3不是装满拉吗 ?(容量只有三)
6是装8大容量的

2006-04-04 22:45
shuiyouhan
Rank: 1
等 级:新手上路
帖 子:32
专家分:0
注 册:2006-1-12
收藏
得分:0 
11楼真高。小弟佩服。

2006-04-15 12:16
快速回复:[讨论][转载]经典问题分酒问题
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.016548 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved