使用 BFS 的水壶问题
您将获得一个 m 升水罐和一个升水罐。两个水壶最初都是空的。水壶没有标记,无法测量较小的数量。您必须使用水罐来测量 d 升水,其中 d 小于 n。
(X, Y) 对应于 X 表示 Jug1 中的水量并且 Y 表示 Jug2 中的水量的状态
确定从初始状态 (xi, yi) 到最终状态 (xf, yf) 的路径,其中 (xi, yi) 为 (0, 0) 表示两个 Jug 最初都是空的, (xf, yf) 表示一个状态可以是 (0, d) 或 (d, 0)。
您可以执行的操作是:
- 清空水壶,(X, Y)->(0, Y) 空水壶 1
- 装满水壶,(0, 0)->(X, 0) 装满水壶 1
- 将水从一个水壶倒到另一个水壶,直到其中一个水壶是空的或满的,(X, Y) -> (Xd, Y+d)
例子:
Input : 4 3 2
Output : {(0, 0), (0, 3), (3, 0), (3, 3), (4, 2), (0, 2)}
我们已经在两个水壶拼图中讨论了最佳解决方案。在这篇文章中,讨论了基于 BFS 的解决方案。
在这里,我们不断探索壶中水状态的所有不同有效情况,直到并且除非我们达到所需的目标水。
正如问题陈述中所提供的,在任何给定的状态下,我们都可以执行以下任一操作:
1. 装满一个水壶
2.清空一个水壶
3. 将水从一个水壶转移到另一个水壶,直到其中一个完全装满或清空。
算法的运行:
我们从队列中两个水壶都是空的初始状态开始。然后,我们继续使用提供的操作探索从当前水罐状态派生的所有可能的中间状态。
我们还维护了一个已访问的状态矩阵,这样我们就可以避免一次又一次地重新访问相同的水罐状态。Cases Jug 1 Jug 2 Is Valid Case 1 Fill it Empty it ✔ Case 2 Empty it Fill it ✔ Case 3 Fill it Fill it Redundant case Case 4 Empty it Empty it Already visited (Initial State) Case 5 Unchanged Fill it ✔ Case 6 Fill it Unchanged ✔ Case 7 Unchanged Empty ✔ Case 8 Empty Unchanged ✔ Case 9 Transfer water from this Transfer water into this ✔ Case 10 Transfer water into this Transfer water from this ✔
从上表中,我们可以观察到两个罐子都装满的状态是多余的,因为我们将无法继续前进/以任何可能的方式对这种状态进行任何操作。
因此,我们继续,记住所有有效的状态案例(如上表所示),并对它们进行 BFS。
在 BFS 中,我们首先跳过已经访问过的状态,或者如果任一壶中的水量超过壶量。
如果继续下去,那么我们首先将当前状态标记为已访问,并检查是否在该状态下,如果我们已经获得了其中一个水壶中的目标水量,我们可以清空另一个水壶并返回当前状态的整个路径。
但是,如果我们还没有找到目标数量,那么我们就从水壶的当前状态推导出中间状态,即我们推导出上表中提到的有效情况(如果您有一些混淆,请查看代码一次)。
我们不断重复上述所有步骤,直到找到目标或没有更多状态可以继续。
Java
//Java program for water jug problem
//using BFS
//Code by: Sparsh_CBS
import java.util.*;
class Pair{
int j1, j2;
List path;
Pair(int j1, int j2){
this.j1 = j1;
this.j2 = j2;
path = new ArrayList<>();
}
Pair(int j1, int j2, List _path){
this.j1 = j1;
this.j2 = j2;
path = new ArrayList<>();
path.addAll(_path);
path.add(new Pair(this.j1,this.j2));
}
}
public class GFG{
public static void main(String[] args) throws java.lang.Exception{
int jug1 = 4;
int jug2 = 3;
int target = 2;
getPathIfPossible(jug1, jug2, target);
}
private static void getPathIfPossible(int jug1, int jug2, int target){
boolean[][] visited = new boolean[jug1+1][jug2+1];
Queue queue = new LinkedList<>();
//Initial State: Both Jugs are empty so,
//initialise j1 j2 as 0 and put it in the path list
Pair initialState = new Pair(0,0);
initialState.path.add(new Pair(0,0));
queue.offer(initialState);
while(!queue.isEmpty()){
Pair curr = queue.poll();
//Skip already visited states and overflowing water states
if(curr.j1 > jug1 || curr.j2 > jug2 || visited[curr.j1][curr.j2])
continue;
//mark current jugs state as visited
visited[curr.j1][curr.j2] = true;
//Check if current state has already reached
//the target amount of water or not
if(curr.j1 == target || curr.j2 == target){
if(curr.j1 == target){
//If in our current state, jug1 holds the
//required amount of water, then we empty the jug2
//and push it into our path.
curr.path.add(new Pair(curr.j1,0));
}
else{
//else, If in our current state, jug2 holds the
//required amount of water, then we empty the jug1
//and push it into our path.
curr.path.add(new Pair(0,curr.j2));
}
int n = curr.path.size();
System.out.println("Path of states of jugs followed is :");
for(int i = 0; i < n; i++)
System.out.println(curr.path.get(i).j1+" , "+curr.path.get(i).j2);
return;
}
//If we have not yet found the target, then we have three cases left
//I. Fill the jug and Empty the other
//II. Fill the jug and let the other remain untouched
//III. Empty the jug and let the other remain untouched
//IV. Transfer amounts from one jug to another
//Please refer to the table attached above to understand
//the cases that we are taking into consideration
//Now,
//I. Fill the jug and Empty the other
queue.offer(new Pair(jug1, 0, curr.path));
queue.offer(new Pair(0, jug2, curr.path));
//II. Fill the jug and let the other remain untouched
queue.offer(new Pair(jug1, curr.j2, curr.path));
queue.offer(new Pair(curr.j1, jug2, curr.path));
//III. Empty the jug and let the other remain untouched
queue.offer(new Pair(0, curr.j2, curr.path));
queue.offer(new Pair(curr.j1, 0, curr.path));
//IV. Transfer water from one to another until one jug becomes empty
//or until one jug becomes full in this process
//Transferring water form jug1 to jug2
int emptyJug = jug2-curr.j2;
int amountTransferred = Math.min(curr.j1, emptyJug);
int j2 = curr.j2+amountTransferred;
int j1 = curr.j1-amountTransferred;
queue.offer(new Pair(j1, j2,curr.path));
//Tranferring water form jug2 to jug1
emptyJug = jug1-curr.j1;
amountTransferred = Math.min(curr.j2, emptyJug);
j2 = curr.j2-amountTransferred;
j1 = curr.j1+amountTransferred;
queue.offer(new Pair(j1, j2,curr.path));
}
System.out.println("Not Possible to obtain target");
}
}
C++
#include
using namespace std;
typedef pair pii;
void printpath(mapmp ,pii u)
{
if(u.first==0 &&u.second==0)
{
cout<<0<<" "<<0<m;
bool isSolvable =false;
vector>path;
mapmp;
queueq;
q.push(make_pair(0,0));
while(!q.empty())
{
auto u =q.front();
// cout< a || u.second > b || u.first < 0 || u.second < 0))
continue;
// cout< jug 2
int d = b - u.second;
if(u.first >= d)
{
int c = u.first - d;
if(m[{c,b}]!=1)
{q.push({c,b});
mp[{c,b}]=u;}
}
else
{
int c = u.first + u.second;
if(m[{0,c}]!=1)
{q.push({0,c});
mp[{0,c}]=u;}
}
//transfer jug 2 -> jug 1
d = a - u.first;
if(u.second >= d)
{
int c = u.second - d;
if(m[{a,c}]!=1)
{q.push({a,c});
mp[{a,c}]=u;}
}
else
{
int c = u.first + u.second;
if(m[{c,0}]!=1)
{q.push({c,0});
mp[{c,0}]=u;}
}
// empty the jug 2
if(m[{u.first,0}]!=1)
{ q.push({u.first,0});
mp[{u.first,0}]=u;}
// empty the jug 1
if(m[{0,u.second}]!=1)
{q.push({0,u.second});
mp[{0,u.second}]=u;}
}
if (!isSolvable)
cout << "No solution";
}
int main()
{
int Jug1 = 5, Jug2 = 7, target = 3;
cout << "Path from initial state "
"to solution state ::\n";
BFS(Jug1, Jug2, target);
return 0;
}
Python3
from collections import deque
def BFS(a, b, target):
# Map is used to store the states, every
# state is hashed to binary value to
# indicate either that state is visited
# before or not
m = {}
isSolvable = False
path = []
# Queue to maintain states
q = deque()
# Initialing with initial state
q.append((0, 0))
while (len(q) > 0):
# Current state
u = q.popleft()
#q.pop() #pop off used state
# If this state is already visited
if ((u[0], u[1]) in m):
continue
# Doesn't met jug constraints
if ((u[0] > a or u[1] > b or
u[0] < 0 or u[1] < 0)):
continue
# Filling the vector for constructing
# the solution path
path.append([u[0], u[1]])
# Marking current state as visited
m[(u[0], u[1])] = 1
# If we reach solution state, put ans=1
if (u[0] == target or u[1] == target):
isSolvable = True
if (u[0] == target):
if (u[1] != 0):
# Fill final state
path.append([u[0], 0])
else:
if (u[0] != 0):
# Fill final state
path.append([0, u[1]])
# Print the solution path
sz = len(path)
for i in range(sz):
print("(", path[i][0], ",",
path[i][1], ")")
break
# If we have not reached final state
# then, start developing intermediate
# states to reach solution state
q.append([u[0], b]) # Fill Jug2
q.append([a, u[1]]) # Fill Jug1
for ap in range(max(a, b) + 1):
# Pour amount ap from Jug2 to Jug1
c = u[0] + ap
d = u[1] - ap
# Check if this state is possible or not
if (c == a or (d == 0 and d >= 0)):
q.append([c, d])
# Pour amount ap from Jug 1 to Jug2
c = u[0] - ap
d = u[1] + ap
# Check if this state is possible or not
if ((c == 0 and c >= 0) or d == b):
q.append([c, d])
# Empty Jug2
q.append([a, 0])
# Empty Jug1
q.append([0, b])
# No, solution exists if ans=0
if (not isSolvable):
print ("No solution")
# Driver code
if __name__ == '__main__':
Jug1, Jug2, target = 4, 3, 2
print("Path from initial state "
"to solution state ::")
BFS(Jug1, Jug2, target)
# This code is contributed by mohit kumar 29
C#
using System;
using System.Collections.Generic;
class GFG{
static void BFS(int a, int b, int target)
{
// Map is used to store the states, every
// state is hashed to binary value to
// indicate either that state is visited
// before or not
Dictionary, int> m = new Dictionary ,int>();
bool isSolvable = false;
List> path = new List>();
// Queue to maintain states
List> q = new List>();
// Initializing with initial state
q.Add(new Tuple(0, 0));
while (q.Count > 0)
{
// Current state
Tuple u = q[0];
// Pop off used state
q.RemoveAt(0);
// If this state is already visited
if (m.ContainsKey(u) && m[u] == 1)
continue;
// Doesn't met jug constraints
if ((u.Item1 > a || u.Item2 > b ||
u.Item1 < 0 || u.Item2 < 0))
continue;
// Filling the vector for constructing
// the solution path
path.Add(u);
// Marking current state as visited
m[u] = 1;
// If we reach solution state, put ans=1
if (u.Item1 == target || u.Item2 == target)
{
isSolvable = true;
if (u.Item1 == target)
{
if (u.Item2 != 0)
// Fill final state
path.Add(new Tuple(u.Item1, 0));
}
else
{
if (u.Item1 != 0)
// Fill final state
path.Add(new Tuple(0, u.Item2));
}
// Print the solution path
int sz = path.Count;
for(int i = 0; i < sz; i++)
Console.WriteLine("(" + path[i].Item1 +
", " + path[i].Item2 + ")");
break;
}
// If we have not reached final state
// then, start developing intermediate
// states to reach solution state
// Fill Jug2
q.Add(new Tuple(u.Item1, b));
// Fill Jug1
q.Add(new Tuple(a, u.Item2));
for(int ap = 0; ap <= Math.Max(a, b); ap++)
{
// Pour amount ap from Jug2 to Jug1
int c = u.Item1 + ap;
int d = u.Item2 - ap;
// Check if this state is possible or not
if (c == a || (d == 0 && d >= 0))
q.Add(new Tuple(c, d));
// Pour amount ap from Jug 1 to Jug2
c = u.Item1 - ap;
d = u.Item2 + ap;
// Check if this state is possible or not
if ((c == 0 && c >= 0) || d == b)
q.Add(new Tuple(c, d));
}
// Empty Jug2
q.Add(new Tuple(a, 0));
// Empty Jug1
q.Add(new Tuple(0, b));
}
// No, solution exists if ans=0
if (!isSolvable)
Console.WriteLine("No solution");
}
// Driver code
static void Main()
{
int Jug1 = 4, Jug2 = 3, target = 2;
Console.WriteLine("Path from initial state " +
"to solution state ::");
BFS(Jug1, Jug2, target);
}
}
// This code is contributed by divyeshrabadiya07
Javascript
Path of states of jugs followed is :
0 , 0
0 , 3
3 , 0
3 , 3
4 , 2
0 , 2
时间复杂度:O(n*m)。
空间复杂度: O(n*m) 。其中 n 和 m 分别是 jug1 和 jug2 的数量。本文已由 Sparsh Sharma 改进。