《算法竞赛入门经典》 7.5 倒水问题

倒水问题

设大、中、小3个杯子的容量分别为a,b,c,最初只有大杯子装满水,其他两个杯子为空。由于没有刻度,用杯子x给杯子y倒水时必须一直持续到把杯子y倒满或者把杯子x倒空,而且不能中途终止。最少需要多少步才能让某一个杯子中的水有x升呢?打印出来每步操作后各个杯子的水量(0<c<b<a<1000).

分析:

题中问最少多少步,宽度优先搜索(BFS)可以解决这种最短路程或者步数的问题。

把3个杯子的状态想象成一个结点,两个结点只能通过倒一次水到达,用一个队列(其实用的是数组)来保存结点,下标front指向的是当前结点,每次把当前结点能到达的子结点(不能和队列中已有的结点重复)加入到队列尾,然后下标front不断向后移动.

代码:

(使用STL 中集合set)

#include<stdio.h>
#include<string.h>
#include<set>
using namespace std;

const int MAX = 10000;
int st[MAX][4];		//状态数组,所有的状态都保存在这; st[i][3]记录父节点下标

int cup[3];	//三个杯子的容量
int x;	//目标量

set<int> vis;

int water(int l,int m,int *buf)	//l向m倒水
{
	if(buf[l] != 0 && buf[m] != cup[m])		//如果l杯中水不为空并且m中水没满
	{
		if(buf[l]+buf[m]<=cup[m])//将l杯中的水倒完
		{
			buf[m] +=buf[l];
			buf[l] = 0;
			return 1;
		}
		else			//将m杯倒满
		{
			buf[l] =buf[l] - (cup[m] - buf[m]);
			buf[m] = cup[m];
			return 1;
		}
		
	}
	return 0;
}

int insert(int *buf)	//如果3个杯子的状态和前面的重复,返回0.否则返回1,并插入集合
{
	int v=0;
	int i;
	for(i=0;i<3;i++)
		v =v*10+buf[i];	//将3个杯子的状态看成一个3位数
	if(vis.count(v)) return 0;
	vis.insert(v);
	return 1;
}

void print(int index)	//递归打印从根节点到目标节点
{
	int i;
	if(!index)	//如果index等于0,说明已到达根节点
		return;
	else
	{
		print(st[index][3]);
		for(i = 0;i < 3;i++)
			printf("%d ",st[index][i]);
		printf("\n");
	}
	return;
}

int main()
{
	int front,rear;
	int i,j;
	int buf[3];		//暂时保存3个杯子状态的数组
	scanf("%d%d%d%d",&cup[0],&cup[1],&cup[2],&x);	//输入3个杯子的容量和目标量

	if(x>cup[0]||x<0) printf("ERROR\n");	//目标量不能大于最大容量,并能小于0

	else
	{
		st[1][0] = cup[0]; st[1][1] = st[1][2] = 0;//根节点初始化,下标从1开始
		st[1][3] = 0;	//根节点没有父节点
		insert(st[1]);
		front = 1;rear = 2;
		while(front < rear)
		{
			if(st[front][0] == x ||st[front][1] == x||st[front][2] == x) //如果已达到目标量,跳出循环
				break;
			memcpy(buf,st[front],sizeof(buf));	//front中的值赋给buf

			//将i杯中的水倒入j杯
			for(i = 0;i < 3;i++)
				for(j = 0;j < 3;j++)
				{
					if(i != j)
					{
						if(water(i,j,buf))	//如果倒水成功
						{
							if(insert(buf))	//如果没有和前面到达的节点重复
							{
								memcpy(st[rear],buf,sizeof(buf));	//将buf中新到达的节点加入队列中
								st[rear][3] = front;			//记录父节点的下标
								rear++;
							}
							memcpy(buf,st[front],sizeof(buf));	//重新赋给buf当前节点的值
						}
					}
				}
			front++;
		}
		if(front < rear)	//front<rear,说明找到了存在目标量的节点
		{
			print(front);	//输出每步操作后的各杯子容量
		}
		else
			printf("没有办法让一个杯子中有%d升水\n",x);
	}	
	scanf("%d",&x);
	return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值