问题一:
问题描述 有一个酒瓶装八斤酒没有量器只有分别装五斤和三斤的空酒瓶。设计程序将八斤酒分为两个四斤求出最少步骤。 输入 问题无需输入。 输出 输出最少步数或者最少步数下的倒酒过程
纯数学解决该问题方法原理 解决问题的一套规则:
1. 大瓶子只能倒入中瓶子
2. 中瓶子只能倒入小瓶子
3. 小瓶子只能倒入大瓶子
4. 小瓶子只有在已经装满的情况下才能倒入大瓶子
5. 若小瓶子被倒空则无论中瓶子是否满应马上从中瓶子倒入小瓶子 之所以要规定倒酒的顺序是为了防止状态重复
而根据这5条规则大瓶子每次倒入中瓶子的酒总是5斤 ,小瓶子每次倒入大瓶子的酒总是3斤。理解这句话理解这点很重要下面会给出理论高度上的描述
上述解题方法的原理设大,中,小三个瓶子容量分别是C1,C2,C3需要倒出的容量是R 则实际上要是我们能将容量为R的酒倒到中瓶子和小瓶子中就可以了。设大瓶子倒满中瓶子X次 从小瓶子中倒入大瓶子Y次。那么显然由大瓶子累次倒入中瓶子和小瓶子总共C2*X的酒。而由小瓶子倒入大瓶子一共有C3*Y的酒。那么最终小瓶子和中瓶子剩余的酒显然就是 C2*X - C3*Y 因此分酒问题实质上转化为下面的不定方程是否有正整数解的问题 C2*X - C3*Y = R 对于我们的问题 C1=8C2=5C3=3R=4 第一种倒酒规则实质上相当于解下面这个不定方程5X - 3Y = 4 限定 X > 0 Y > 0 最小整数解是 X=2Y= 2 表示倒满5斤的瓶子2次3斤的瓶子倒空2次
那么5斤的瓶子和3斤的瓶子剩酒总量必然是4斤了现在你明白为什么要规定倒酒的顺序了吧。小瓶子和中瓶子是一个系统而大瓶子又是另外一个系统大瓶子的酒只能倒入中瓶子和小瓶子组成的系统小瓶子的酒只能倒出到大瓶子的系统。我们关注的是由中瓶子和小瓶子组成的系统这个系统每次增加都是5斤中瓶子容量每次减少都是3斤小瓶子容量。 另外如果存在X和Y使得下面的方程有解 C2*X - C3*Y = 1 实质上就是说能够倒出1斤的酒那么任意斤的酒都能倒出了。 因为: (C2*X - C3*Y)*N = N
根据不定方程写出分酒问题代码如:
public class BoSongFenJiu1 {
private static int A=8;
private static int B=5;
private static int C=3;
private static int a=A;
private static int b=0;
private static int c=0;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(a+" "+b+" "+c);
while(a!=A/2)
{
if(b==0)
{
A_B();
}
if(c==C)
{
C_A();
}
else if(b!=0)
{
B_C();
}
}
}
public static void A_B()
{
b=a>B?B:a;
a-=b;
System.out.println(a+" "+b+" "+c);
}
public static void B_C()
{
int n=c;
c=b+c>C?C:b+c;
b-=c-n;
System.out.println(a+" "+b+" "+c);
}
public static void C_A()
{
a=a+C;
c=0;
System.out.println(a+" "+b+" "+c);
}
}
深度搜索实现的代码如下:
import java.util.LinkedList;
import java.util.Queue;
/*泊松分酒(深度优先算法)
*
* 问题描述 有一个酒瓶装八斤酒没有量器只有分别装五斤和三斤的空酒瓶。设计程序将八斤
* 酒分为两个四斤求出最少步骤。 输入 问题无需输入。 输出 输出最少步数或者最少步
* 数下的倒酒过程
* 输出最小步数
* */
public class BoSongFenJiu12 {
private static int num=0;//标记出现多少不同 的状态
private static boolean[][] a=new boolean[9][6];//标记状态是否已经出现过,三个数组成的一个状态用一个二维数组
//就可以判断是否重复,是因为三个数的和是一定的
private static Queue q;//队列用来存储出现的各种状态
private static int[] cap={8,5,3};//三个酒瓶的容量
public class Node
{
//保存一种状态下三个酒瓶的斤数,从最大容量到最小容量
public int[] state=new int[3];
}
private static Node curState;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Init();
BoSongFenJiu12 b=new BoSongFenJiu12();
/*因为node记录的是步数,所以随时有可能结束,需要输出,所以在这个地方声明,不能再内部声明*/
BoSongFenJiu12.Node node=b.new Node();
BoSongFenJiu12.Node temp=b.new Node();
while(q.size()>1)
{
curState=((Node)q.poll());
/*当遇到curState.state[1]<0的时候说明该层已经遍历完了,需要遍历下一层
* 此时步数需要加1,此处为了与其他结点冲突,采用负数来记录步数,最后输出的时候
* 输出该值的负数即可*/
if(curState.state[1]<0)
{
node.state[1]=curState.state[1]-1;//记录步数
q.offer(node);
}
else
{
/*将当前状态保存一下,因为下一次循环还需要用到初始的值
* 注意注意:对象之间放入赋值赋的是对象的指针,任一个指向同以对象
* 的指针将对象的数据改变的话,内容就会改变
* 此处不能直接赋指针,*/
temp.state[0]=curState.state[0];
temp.state[1]=curState.state[1];
temp.state[2]=curState.state[2];
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
int n=0;
if((n=pour(i,j))>0)
{
move(i,j,n);
if(hasExit()==0)
{
num++;
if(isArm(curState)==1)
{
System.out.println(-node.state[1]);
System.exit(0);//成功退出
}
BoSongFenJiu12.Node temp2=b.new Node();
temp2.state[0]=curState.state[0];
temp2.state[1]=curState.state[1];
temp2.state[2]=curState.state[2];
q.offer(temp2);//将生成的新状态放入队列中
}
//恢复初始状态进行下一状态求解
//curState=temp;此处也不能直接赋指针,因为要保证在curState变的时候
//temp不变
curState.state[0]=temp.state[0];
curState.state[1]=temp.state[1];
curState.state[2]=temp.state[2];
}
}
}
}
}
}
//初始化函数
public static void Init()
{
q=new LinkedList();
BoSongFenJiu12 b=new BoSongFenJiu12();
BoSongFenJiu12.Node node=b.new Node();
BoSongFenJiu12.Node initState=b.new Node();
initState.state[0]=8;
initState.state[1]=0;
initState.state[2]=0;
node.state[0]=-1;//加入队列负数进行计数标记,为了输出最少步数
node.state[1]=-1;
node.state[2]=-1;
q.offer(initState);
q.offer(node);
}
//从i到j酒瓶倒酒 返回倒酒的斤数
public static int pour(int i,int j)
{
if(i==j)//自己向自己倒酒
return 0;
else if(curState.state[i]<=0)//i 中没有酒可以倒
return 0;
else
{
if(i==0)//代表8斤的瓶子
{
if(j==1)// 代表5斤的瓶子
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
else if(j==2)//代表3斤的瓶子
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
}
else if(i==1)
{
if(j==0)
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
else if(j==2)
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
}
else if(i==2)
{
if(j==0)
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
else if(j==1)
{
int empty=cap[j]-curState.state[j];
return curState.state[i]>empty?empty:curState.state[i];
}
}
return 0;
}
}
//移动函数 i酒瓶向j酒瓶倒了n斤的酒
public static void move(int i,int j,int n)
{
curState.state[j]+=n;
curState.state[i]-=n;
}
//判重函数(因为有可能几个状态经过各种可能的倒酒情况之后可能到达同一状态,当出现重复状态时不需要再将
//结点放入队列中)
public static int hasExit()
{
if(a[curState.state[0]][curState.state[1]]==true)//状态已经存在
{
return 1;
}
else
{
a[curState.state[0]][curState.state[1]]=true;
return 0;
}
}
//判断是否达到目标状态
public static int isArm(Node n)
{
if(n.state[0]==4&&n.state[1]==4&&n.state[2]==0)
return 1;
else
return 0;
}
}
上面程序有一个问题:当一步可以完成的时候,输出的结果是0,可以根据上面的程序稍加修改即可。注意该题深度优先用队列来实现的步骤,和没遍历完一层,步数加1的技巧。
该问题的扩展:
泊松是法国数学家、物理学家和力学家。他一生致力科学事业,成果颇多。有许多著名的公式定理以他的名字命名,比如概率论中著名的泊松分布。
有一次闲暇时,他提出过一个有趣的问题,后称为:“泊松分酒”。在我国古代也提出过类似问题,遗憾的是没有进行彻底探索,其中流传较多是:“韩信走马分油”问题。
有3个容器,容量分别为12升,8升,5升。其中12升中装满油,另外两个空着。要求你只用3个容器操作,最后使得某个容器中正好有6升油。
下面的列表是可能的操作状态记录:
12,0,0
4,8,0
4,3,5
9,3,0
9,0,3
1,8,3
1,6,5
每行3个数据,分别表示12,8,6升容器中的油量
第一行表示初始状态,第二行表示把12升倒入8升容器后的状态,第三行是8升倒入5升,...
当然,同一个题目可能有多种不同的正确操作步骤。
本题目的要求是,请你编写程序,由用户输入:各个容器的容量,开始的状态,和要求的目标油量,程序则通过计算输出一种实现的步骤(不需要找到所有可能的方法)。如果没有可能实现,则输出:“不可能”。
例如,用户输入:
12,8,5,12,0,0,6
用户输入的前三个数是容器容量(由大到小),接下来三个数是三个容器开始时的油量配置,最后一个数是要求得到的油量(放在哪个容器里得到都可以)
则程序可以输出(答案不唯一,只验证操作可行性):
12,0,0
4,8,0
4,3,5
9,3,0
9,0,3
1,8,3
1,6,5
每一行表示一个操作过程中的油量状态。
上面的问题只需要将酒杯的额容量和条件改改就好了!输入的数据要进行验证,如果每个容器里剩下的比容器的容量还多,肯定错误,如果最后每个容器剩余的酒的和比初始的酒还多,也肯定错误!
该题的程序只要将上面的问题的程序,数值修改一下即可!