倒水问题 (BFS搜索问题)

解决两个无刻度水壶如何通过倒水操作量出特定升数的水,使用BFS算法寻找最少步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述 Description  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold


        有两个无刻度标志的水壶,分别可装 x 升和 y 升 ( x,y 为整数且均不大于 100 )的水。设另有一水 缸,可用来向水壶灌水或接从水壶中倒出的水, 两水壶间,水也可以相互倾倒。已知 x 升壶为空 壶, y 升壶为空壶。问如何通过倒水或灌水操作, 用最少步数能在x或y升的壶中量出 z ( z ≤ 100 )升的水来。

        输入描述 Input Description
         一行,三个数据,分别表示 x,y 和 z;

        输出描述 Output Description
         一行,输出最小步数 ,如果无法达到目标,则输出"impossible"

        样例输入 Sample Input
         3 22 1

        样例输出 Sample Output
        14

解题思路:列举出所有的倒水操作,题目中未给出容器名称,这里假定A容器的容量为x升,B容器的容量为y升

        1、将A容器中的全部水倒入水缸(当A容器中有水时)

        2、将B容器中的全部水倒入水缸(当B容器中有水时)

        3、将A容器装满水(当A容器中的水未满时)

        4、将B容器装满水(当B容器中的水未满时)

        5、A容器中的水倒入B容器(当A容器中有水,同时B容器中水未满时)

        第五种倒水操作还可分为两类:

              情况1:如果A容器中的水小于等于B容器的剩余容量,则操作过后A容器中剩余水量为0;

              情况2:如果A容器中的水大于B容器的剩余容量,则操作过后A容器中剩余水量为:

              A容器当前装水量 - B容器的剩余容量;

        6、B容器中的水倒入A容器(当B容器中有水,同时A容器中水未满时)

         第六种倒水操作还可分为两类:

              情况1:如果B容器中的水小于等于A容器的剩余容量,则操作过后B容器中剩余水量为0;

              情况2:如果B容器中的水大于A容器的剩余容量,则操作过后B容器中剩余水量为:

              B容器当前装水量 - A容器的剩余容量;

实现代码:

​
package algorithm.bfs;

import java.util.LinkedList;
import java.util.Scanner;

/**
 * 题目描述 Description
 *      有两个无刻度标志的水壶,分别可装 x 升和 y 升 ( x,y 为整数且均不大于 100 )的水。
 *      设另有一水 缸,可用来向水壶灌水或接从水壶中倒出的水, 两水壶间,水也可以相互倾倒。
 *      已知 x 升壶为空 壶, y 升壶为空壶。问如何通过倒水或灌水操作, 用最少步数能在x或y升的壶
 *      中量出 z ( z ≤ 100 )升的水 来。
 *
 *      输入描述 Input Description
 *      一行,三个数据,分别表示 x,y 和 z;
 *
 *      输出描述 Output Description
 *      一行,输出最小步数 ,如果无法达到目标,则输出"impossible"
 *
 *      样例输入 Sample Input
 *      3 22 1
 *
 *      样例输出 Sample Output
 *      14
 * @author guojm
 *
 */

class Nodes {
	public int x;   //当前节点的A水壶所装水的体积(升)
	public int y;   //当前节点的B水壶所装水的体积(升)
	public int current; //当前节点所在层数,用于求最小步数
}
public class PourWater {
	int visited[][] = new int[100][100];  //记录该节点是否访问过
	static int a, b, c;
	Nodes n1, n2;
	Nodes t;
	public boolean bfs(int x, int y) {
		boolean ok = false;
        //用LinkedList模拟队列操作
		LinkedList<Nodes> q = new LinkedList<Nodes>();
		n1 = new Nodes();
	    n1.x = x;
	    n1.y = y;
	    n1.current = 0;
	    q.addLast(n1);
	    while(!q.isEmpty()) {
            //取出第一个节点元素(Nodes对象)
	    	n2 = q.getFirst();
            //从模拟队列中移除(Nodes对象)
	    	Nodes x1 = q.removeFirst();
 	
	    	//如果A容器或者B容器得到目标c升水
	    	if(n2.x == c || n2.y == c) {
	    		 //输出当前结点所在的层数
	    		 System.out.println(n2.current);
	    		 //ok设置为true,表明倒水问题有解
	    	     ok = true;           
	    	     break;
	    	}
	    	if(visited[n2.x][n2.y] == 1) continue; //结点重复则剪枝处理
	    	//当前状态没出现过,则标志已访问
	    	visited[n2.x][n2.y] = 1;
	    	
	    	for(int i = 1; i <= 6; i++) {   
	    		 t = new Nodes();
	    		 
	             t.current = n2.current + 1;  //记录每个入栈结点的层数
	             
	             //i=1 并且A容器中有水时,倒水操作为:将A容器中的全部水倒入水 缸
	             if(i == 1 && n2.x > 0)  { t.x = 0; t.y = n2.y;  q.addLast(t); }
	             
	             //i=2 并且B容器中有水时,倒水操作为:将B容器中的全部水倒入水 缸
	             if(i == 2 && n2.y > 0)  { t.y = 0; t.x = n2.x;  q.addLast(t); }
	             
	             //i=3 并且A容器中的水未满时,倒水操作为:将A容器装满水
	             if(i == 3 && n2.x != a) { t.x = a; t.y = n2.y;  q.addLast(t); } 
	             
	             //i=4 并且B容器中的水未满时,倒水操作为:将B容器装满水
	             if(i == 4 && n2.y != b) { t.y = b; t.x = n2.x;  q.addLast(t); }
	             
	             //i=5并且A容器中有水,同时B容器中水未满时,倒水操作为:A容器中的水倒入B容器
	             if(i == 5 && n2.x != 0 && (b - n2.y) != 0) 
	             {  
	            	//如果A容器中的水小于等于B容器的剩余容量
	                if(n2.x <= (b - n2.y))
	                  {  t.x = 0; t.y = n2.y + n2.x;  q.addLast(t); }
	                //如果A容器中的水大于B容器的剩余容量
	                else
	                  {  t.y = b; t.x = n2.x - (b - n2.y); q.addLast(t); }
	             }
	            //i=6并且B容器中有水,同时A容器中水未满时,倒水操作为:B容器中的水倒入A容器
	            if(i == 6 && n2.y != 0 && (a - n2.x) != 0) 
	            {  
	               //如果B容器中的水小于等于B容器的剩余容量
	               if(n2.y <= (a - n2.x))
	                 {  t.y = 0; t.x = n2.x + n2.y; q.addLast(t); }
	               //如果B容器中的水大于A容器的剩余容量
	               else
	                 {  t.x = a; t.y = n2.y - (a - n2.x);  q.addLast(t); }
	            }
	         }
	      }
		return ok;
	}
	public static void main(String[] args) {
		PourWater p = new PourWater();
		@SuppressWarnings("resource")
		Scanner scan = new Scanner(System.in);
		a = scan.nextInt();
		b = scan.nextInt();
		c = scan.nextInt();
		if(!p.bfs(0,0)) System.out.println("impossible");
	}
	

}

​

运行结果:

<think>嗯,用户想用C++的BFS算法来解决倒水问题倒水问题应该就是经典的罐子倒水问题吧,比如有三个罐子,容量不同,通过倒水得到特定容量。首先,我需要回忆一下BFS的基本结构。BFS是广度优先搜索,用队列来实现,逐层探索可能的节点,直到找到解决方案。这样的话,每个状态都是一个节点,节点之间通过不同的倒水操作连接。 接下来,我得确定如何表示罐子的状态。假设有两个罐子,容量分别是A和B,当前量分别是a和b,那么状态可以用一个pair或者结构体来保存(a, b)。这样的话,每次操作可以有几种可能性:倒满一个罐子、倒一个罐子、将一个罐子的倒入另一个直到倒满或者倒。这些操作会产生新的状态,需要将这些状态加入队列中。 然后,我需要考虑如何避免重复访问已经处理过的状态。可以用一个二维数组或者哈希表来记录已经访问过的状态,比如visited[A+1][B+1],因为每个罐子的量不会超过其容量。 然后,BFS的队列初始化的时候应该是初始状态,比如两个罐子都是的,或者根据问题具体情况来定。例如,题目可能是要得到某个罐子中有特定容量,比如其中一个罐子有C升。这时候,每次从队列取出一个状态时,都要检查是否满足条件,如果满足就返回当前的步数或者路径。 不过用户提到的是倒水问题,可能是指经典的问题。比如,给定两个罐子的容量,如何通过倒水得到恰好某个容量。这时候,可能的操作包括: 1. 倒满一个罐子。 2. 倒一个罐子。 3. 将一个罐子倒入另一个,直到倒出罐子或者倒入罐子满。 所以,每个状态转换需要生成所有可能的操作后的新状态,并加入队列中。 接下来,我需要考虑如何用C++代码实现这个逻辑。首先定义状态结构体或者用pair<int, int>,然后队列存储这些状态。同时维护一个访问数组来记录是否已经处理过该状态。 举个例子,假设罐子的容量是x和y,目标容量是z。初始状态是(0,0)。然后,每次从队列取出一个状态,生成所有可能的下一步状态,比如: - 倒满第一个罐子:(x, b) - 倒满第二个罐子:(a, y) - 倒一个罐子:(0, b) - 倒第二个罐子:(a, 0) - 将第一个罐子的倒入第二个罐子,直到第二个满或者第一个。这时候,转移后的量需要计算。比如,倒入后的第二个罐子的量是min(b + a, y),而第一个罐子的剩余量是a - (y - b) 如果倒不满,否则0。或者,更准确的是,倒入量是min(a, y - b),所以新的状态是(a -倒入量, b +倒入量)。 - 同样,将第二个罐子的倒入第一个罐子。 这样,每个状态可以生成最多6种可能的新状态。不过需要注意,比如倒满或倒操作可能生成与原状态相同的状态,这时候需要跳过,避免重复。 然后,BFS每次处理一个节点,记录步数,当达到目标状态时,返回当前步数。如果队列了还没找到,说明无解。 代码的大体结构应该是这样的: struct State { int a; int b; int steps; }; 然后队列里保存这个结构体。初始状态是(0,0),steps为0。每次处理队列中的一个节点,检查是否满足条件(比如a == z或者 b == z或者 a + b == z,视具体问题而定),如果满足则返回steps。否则,生成所有可能的下一步状态,并检查是否已经被访问过,如果没有则加入队列,并标记为已访问。 需要注意的是,如果罐子数量不是两个而是更多,比如三个,那么状态结构会更复杂,但用户的问题可能是指两个罐子的情况。 可能用户的问题是要解决特定的倒水问题,比如给定两个罐子的容量,如何得到某个目标容量。所以,代码需要根据这个逻辑来写。 另外,在BFS中,每个状态的steps需要正确记录,确保每次操作增加一步。 现在,我需要将这些思路整理成C++代码。例如: #include <iostream> #include <queue> #include <set> using namespace std; struct State { int jugA; int jugB; int steps; }; int bfs(int capacityA, int capacityB, int target) { queue<State> q; set<pair<int, int>> visited; q.push({0, 0, 0}); visited.insert({0, 0}); while (!q.empty()) { State current = q.front(); q.pop(); if (current.jugA == target || current.jugB == target || current.jugA + current.jugB == target) { return current.steps; } // 生成所有可能的状态 vector<State> nextStates; // 倒满A nextStates.push_back({capacityA, current.jugB, current.steps + 1}); // 倒满B nextStates.push_back({current.jugA, capacityB, current.steps + 1}); // 倒A nextStates.push_back({0, current.jugB, current.steps + 1}); // 倒B nextStates.push_back({current.jugA, 0, current.steps + 1}); // A倒到B int pour = min(current.jugA, capacityB - current.jugB); nextStates.push_back({current.jugA - pour, current.jugB + pour, current.steps + 1}); // B倒到A pour = min(current.jugB, capacityA - current.jugA); nextStates.push_back({current.jugA + pour, current.jugB - pour, current.steps + 1}); for (const auto& state : nextStates) { pair<int, int> key = {state.jugA, state.jugB}; if (visited.find(key) == visited.end()) { visited.insert(key); q.push(state); } } } return -1; // 无解 } int main() { int A = 5, B = 3, target = 4; int result = bfs(A, B, target); if (result != -1) { cout << "最少需要 " << result << " 步" << endl; } else { cout << "无解" << endl; } return 0; } 不过可能还需要测试,比如当罐子容量为5和3,目标是4的时候,是否正确返回步骤数。例如,可能的步骤是: 1. 填满5升罐子,倒入3升,剩下2。 2. 倒3,将5中的2倒入3。 3. 填满5,倒入3,此时3中有2,倒入后3满,5剩下4。 这样需要6步?或者可能更少? 不过代码的逻辑是否正确呢?可能需要注意状态转换是否正确。比如,在倒水的时候,计算倒入的量是否正确。 此外,是否需要在每次操作后检查是否达到目标?比如,可能在中间步骤就满足了目标,例如当倒满其中一个罐子时刚好等于目标。 另外,访问过的状态需要用pair来记录,确保不会重复处理同样的状态。 这样,用户的问题应该可以通过这个BFS算法解决。不过需要根据具体的问题要求调整目标判断条件。比如,有些问题可能要求其中一个罐子正好是目标量,或者总共有目标量。 总结来说,BFS适用于这类状态转换问题,因为可以找到最少步骤的解。需要注意的是状态的正确生成和避免重复访问。</think>### C++ BFS算法解决倒水问题 倒水问题要求通过两个壶的操作得到特定容量。以下是基于BFS的解决方案: #### **1. 状态表示与操作** 用结构体`JugsState`记录当前状态: ```cpp struct JugsState { int jugA; // 当前A壶量 int jugB; // 当前B壶量 int steps; // 已用步数 }; ``` **六种可能操作**: 1. 倒满A壶:$(a, b) \rightarrow (A_{max}, b)$ 2. 倒满B壶:$(a, b) \rightarrow (a, B_{max})$ 3. 倒A壶:$(a, b) \rightarrow (0, b)$ 4. 倒B壶:$(a, b) \rightarrow (a, 0)$ 5. A倒入B:$a_{\text{新}} = \max(0, a - (B_{max} - b))$,$b_{\text{新}} = \min(B_{max}, b + a)$ 6. B倒入A:$b_{\text{新}} = \max(0, b - (A_{max} - a))$,$a_{\text{新}} = \min(A_{max}, a + b)$ #### **2. BFS实现模板** ```cpp #include <iostream> #include <queue> #include <set> using namespace std; struct JugsState { int jugA, jugB, steps; }; int water_jug_bfs(int capacityA, int capacityB, int target) { queue<JugsState> q; set<pair<int, int>> visited; q.push({0, 0, 0}); visited.insert({0, 0}); while (!q.empty()) { JugsState current = q.front(); q.pop(); // 终止条件:任一壶或总量等于目标 if (current.jugA == target || current.jugB == target || current.jugA + current.jugB == target) { return current.steps; } // 生成所有操作后的状态 vector<JugsState> next_states = { {capacityA, current.jugB, current.steps + 1}, // 倒满A {current.jugA, capacityB, current.steps + 1}, // 倒满B {0, current.jugB, current.steps + 1}, // 倒A {current.jugA, 0, current.steps + 1}, // 倒B {max(0, current.jugA - (capacityB - current.jugB)), min(capacityB, current.jugB + current.jugA), current.steps + 1}, // A→B {min(capacityA, current.jugA + current.jugB), max(0, current.jugB - (capacityA - current.jugA)), current.steps + 1} // B→A }; // 检查并加入队列 for (const auto& state : next_states) { pair<int, int> key(state.jugA, state.jugB); if (!visited.count(key)) { visited.insert(key); q.push(state); } } } return -1; // 无解 } ``` #### **3. 示例调用** ```cpp int main() { int steps = water_jug_bfs(5, 3, 4); if (steps != -1) cout << "最少步数: " << steps << endl; else cout << "无解" << endl; return 0; } ``` #### **4. 性能与优化** - **时间复杂度**:最坏情况为$O(A \times B)$,其中$A$、$B$为壶的容量[^2]。 - **间优化**:使用哈希集合`unordered_set`替代`set`可提升速度[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值