Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >算法系列之深度/广度优先搜索解决水桶分水的最优解及全部解

算法系列之深度/广度优先搜索解决水桶分水的最优解及全部解

原创
作者头像
修己xj
发布于 2025-03-11 23:51:32
发布于 2025-03-11 23:51:32
890
举报
文章被收录于专栏:修己xj修己xj

在算法学习中,广度优先搜索(BFS)适用于解决最短路径问题、状态转换问题等。深度优先搜索(DFS)适合路径搜索等问题。本文将介绍如何利用广度优先搜索解决寻找3 个 3、5、8 升水桶均分 8 升水的最优解及深度优先搜索寻找可以解决此问题的所有解决方案。

问题描述

我们有三个水桶,容量分别为 3 升、5 升和 8 升。初始状态下,8 升的水桶装满水,其他两个水桶为空。我们的目标是通过一系列倒水操作,最终使得 8 升水桶中的水被均分,即8升和5升的水桶中都有 4 升水。我们需要找到最少操作次数以及所有可能的操作路径。

问题分析

首先,我们需要明确每个水桶的容量和当前的水量。我们可以将每个状态表示为一个对象的三个属性(A, B, C),其中A、B、C分别代表三个水桶中的水量。我们的目标是从(0, 0, 8)到(0, 4, 4)。

为了系统地解决这个问题,我们可以将状态变化看作是一个图搜索问题。每个状态是一个节点,从一个状态到另一个状态的合法倒水操作是边。我们的目标是从初始节点到目标节点找到一条路径。最短路径我们用广度优先搜索(BFS)实现,所有可能的操作我们用深度优先搜索(DFS)实现。

状态属性如下:

代码语言:java
AI代码解释
复制
int bucket3; //3升水桶的水量
int bucket5; //5升水桶的水量
int bucket8; //8升水桶的水量

我们倒水一共有6中可能,bucket3->bucket5,bucket3->bucket8,bucket5->bucket3,bucket5->bucket8,bucket8->bucket3,bucket8->bucket5,且每次倒水桶中必须有水,接水桶中水没有满容量。每次操作完后倒水桶为空或者接水桶为满。

我们下图展示了一条分支的状态树结构:

代码实现

以下是Java实现的分水问题解决代码:

代码语言:java
AI代码解释
复制
/**
 * 3个水桶等分8升水的问题
 *
 * 问题描述:有3个容积分别为3升,5升,8升的水桶,初始时,只有8升水的水桶中装满了水,其他两个水桶是空的。
 *          3个水桶都没有刻度,现在需要将8升水分成相等的两份,只能借助于另外两个水桶。
 * 求解结果: 1、步骤最少的分水方案
 *          2、所有能达成目标的分水方案
 *
 */
public class WaterBucketSeparation {

    /**
     * 水桶状态类
     */
    public static class BucketState {
        int bucket3; //3升水桶的水量
        int bucket5; //5升水桶的水量
        int bucket8; //8升水桶的水量
        BucketState prev; //上一个状态,用于回溯步骤

        public BucketState(int bucket3, int bucket5, int bucket8) {
            this.bucket3 = bucket3;
            this.bucket5 = bucket5;
            this.bucket8 = bucket8;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            BucketState that = (BucketState) o;
            return bucket3 == that.bucket3 && bucket5 == that.bucket5 && bucket8 == that.bucket8 ;
        }

        @Override
        public int hashCode() {
            return Objects.hash(bucket3, bucket5, bucket8);
        }

        @Override
        public String toString() {
            return "[" + bucket3 +
                    ", " + bucket5 +
                    ", " + bucket8 +
                    "] ";
        }
    }

    /**
     * 判断分水是否成功,只有5升水桶和8升水桶都装4升水的时候,分水成功。
      */

    public static boolean isSuccess(BucketState state) {
        return state.bucket3 == 0 && state.bucket5 == 4 && state.bucket8 == 4;
    }

    /**
     * 检查状态是否有效,3升水桶的水量[0,3],5升水桶的水量[0,5],8升水桶的水量[0,8]。
     */
    public static boolean checkState(BucketState state){
        return state.bucket3 >= 0 && state.bucket3 <= 3 &&
                state.bucket5 >= 0 && state.bucket5 <= 5 &&
                state.bucket8 >= 0 && state.bucket8 <= 8;
    }

    /**
     * 生成下一步状态
     * 一共有6种操作:
     * 3->5,3->8,5->3,5->8,8->3,8->5,且每次倒水桶中必须有水,接水桶中水没有满容量。每次操作完后倒水桶为空或者接水桶为满。
     */
    public static List<BucketState> generateNextStates(BucketState state) {
        List<BucketState> nextStates = new ArrayList<>();
        int bucket3 = state.bucket3;
        int bucket5 = state.bucket5;
        int bucket8 = state.bucket8;

        // 3 -> 5
        pourWater(bucket3, 3, bucket5, 5, bucket8, nextStates, state);
        // 3 -> 8
        pourWater(bucket3, 3, bucket8, 8, bucket5, nextStates, state);
        // 5 -> 3
        pourWater(bucket5, 5, bucket3, 3, bucket8, nextStates, state);
        // 5 -> 8
        pourWater(bucket5, 5, bucket8, 8, bucket3, nextStates, state);
        // 8 -> 3
        pourWater(bucket8, 8, bucket3, 3, bucket5, nextStates, state);
        // 8 -> 5
        pourWater(bucket8, 8, bucket5, 5, bucket3, nextStates, state);

        return nextStates;

    }

    /**
     * 倒水操作及状态判断
     */
    private static void pourWater(int fromAmount, int fromCapacity, int toAmount, int toCapacity, int otherBucket, List<BucketState> nextStates, BucketState currentState) {
        //倒水判断及下一个状态构建
        if (fromAmount > 0 && toAmount < toCapacity) {
            int pourAmount = Math.min(fromAmount, toCapacity - toAmount);
            int newFromAmount = fromAmount - pourAmount;
            int newToAmount = toAmount + pourAmount;

            BucketState newState;
            if (fromCapacity == 3) {
                newState = new BucketState(newFromAmount, toCapacity == 5 ? newToAmount : otherBucket, toCapacity == 8 ? newToAmount : otherBucket);
            } else if (fromCapacity == 5) {
                newState = new BucketState(toCapacity == 3 ? newToAmount : otherBucket, newFromAmount, toCapacity == 8 ? newToAmount : otherBucket);
            } else {
                newState = new BucketState(toCapacity == 3 ? newToAmount : otherBucket, toCapacity == 5 ? newToAmount : otherBucket, newFromAmount);
            }
            //检查状态
            if (checkState(newState)) {
                newState.prev = currentState;
                nextStates.add(newState);
            }
        }
    }


    /**
     * 广度优先搜索求解最优解
     */
    public static BucketState bfs(BucketState state){
        Queue<BucketState> queue = new LinkedList<>();
        Set<BucketState> visited = new HashSet<>();
        queue.add(state);
        while (!queue.isEmpty()) {
            BucketState currentState = queue.poll();
            visited.add(currentState);
            if (isSuccess(currentState)) {
                return currentState;
            }

            List<BucketState> nextStates = generateNextStates(currentState);
            for (BucketState nextState : nextStates){
                if (!visited.contains(nextState)) {
                    queue.add(nextState);
                    visited.add(nextState);
                }
            }
        }
        return null;
    }


    /**
     * 深度优先搜索求解所有可能解
     */

    public static void dfs(BucketState state,Set<BucketState> visited, List<BucketState> result){
        if(isSuccess(state)){
            result.add(state);
            return;
        }
        //递归深度优先搜索
        for(BucketState nextState : generateNextStates(state)){
            if(!visited.contains(nextState)){
                visited.add(nextState);
                dfs(nextState,visited,result);
                visited.remove(nextState);
            }
        }
    }

    /**
     * 路径打印
     */
    public static void printPath(BucketState state) {
        Stack<BucketState> stack = new Stack<>();
        while (state != null) {
            stack.push(state);
            state = state.prev;
        }

        int step = 1;
        BucketState prevState = null;
        while (!stack.isEmpty()) {
            BucketState currentState = stack.pop();
            if (prevState != null) {
                System.out.println(step + "、" + prevState.toString() + "----->" + currentState.toString());
                step++;
            }
            prevState = currentState;
        }
    }


    public static void main(String[] args) {
        BucketState initState = new BucketState(0, 0, 8);

        // BFS 寻找最短路径
        BucketState bfsResult = bfs(initState);
        if (bfsResult == null) {
            System.out.println("无法实现");
        } else {
            System.out.println("最短路径:");
            printPath(bfsResult);
        }

        // DFS 寻找所有路径
        List<BucketState> dfsResults = new ArrayList<>();
        dfs(initState, new HashSet<>(), dfsResults);
        System.out.println("\n共有"+dfsResults.size()+"种解");
        System.out.println("\n所有路径:");
        for (int i = 0; i < dfsResults.size(); i++) {
            System.out.println("方式 " + (i + 1) + ":");
            printPath(dfsResults.get(i));
        }
    }

}
代码语言:txt
AI代码解释
复制
最短路径:
1、[0, 0, 8] ----->[0, 5, 3] 
2、[0, 5, 3] ----->[3, 2, 3] 
3、[3, 2, 3] ----->[0, 2, 6] 
4、[0, 2, 6] ----->[2, 0, 6] 
5、[2, 0, 6] ----->[2, 5, 1] 
6、[2, 5, 1] ----->[3, 4, 1] 
7、[3, 4, 1] ----->[0, 4, 4] 

共有23种解

所有路径:
方式 1:
1、[0, 0, 8] ----->[3, 0, 5] 
2、[3, 0, 5] ----->[0, 3, 5] 
3、[0, 3, 5] ----->[0, 0, 8] 
4、[0, 0, 8] ----->[0, 5, 3] 
5、[0, 5, 3] ----->[3, 2, 3] 
6、[3, 2, 3] ----->[0, 2, 6] 
7、[0, 2, 6] ----->[2, 0, 6] 
8、[2, 0, 6] ----->[2, 5, 1] 
9、[2, 5, 1] ----->[3, 4, 1] 
10、[3, 4, 1] ----->[0, 4, 4] 
方式 2:
.....

通过这段代码,我们总共找到了23种倒水的方式,最短操作的方式只有7步。

总结

通过 BFS\DFS 算法,我们可以有效地解决水桶均分问题,并找到所有可能的操作路径。这个方法不仅适用于 3、5、8 升水桶的问题,还可以推广到其他类似的最短路径及路径搜索等问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
算法系列之深度/广度优先搜索解决水桶分水的最优解及全部解
在算法学习中,广度优先搜索(BFS)适用于解决最短路径问题、状态转换问题等。深度优先搜索(DFS)适合路径搜索等问题。本文将介绍如何利用广度优先搜索解决寻找3 个 3、5、8 升水桶均分 8 升水的最优解及深度优先搜索寻找可以解决此问题的所有解决方案。
修己xj
2025/03/12
1140
算法系列之深度/广度优先搜索解决水桶分水的最优解及全部解
算法系列之广度优先搜索解决妖怪和尚过河问题
在算法学习中,广度优先搜索(BFS)是一种常用的图搜索算法,适用于解决最短路径问题、状态转换问题等。本文将介绍如何利用广度优先搜索解决经典的“妖怪和尚过河问题”。
修己xj
2025/03/12
580
算法系列之广度优先搜索解决妖怪和尚过河问题
Python 算法高级篇:深度优先搜索和广度优先搜索的高级应用
深度优先搜索( DFS )和广度优先搜索( BFS )是图算法中的两个基本搜索算法,它们用于遍历和搜索图或树结构。这两种算法不仅在计算机科学中具有重要地位,还在现实世界的各种应用中发挥着关键作用。在本文中,我们将深入探讨 DFS 和 BFS 的高级应用,包括拓扑排序、连通性检测、最短路径问题等,并提供详细的代码示例和注释。
小蓝枣
2023/11/01
8820
Python 算法基础篇之图的遍历算法:深度优先搜索和广度优先搜索
图的遍历是计算机科学中的一项重要任务,用于查找和访问图中的所有节点。深度优先搜索( DFS )和广度优先搜索( BFS )是两种常用的图遍历算法。本篇博客将重点介绍这两种算法的原理、应用场景以及使用 Python 实现,并通过实例演示每一行代码的运行过程。
小蓝枣
2023/07/25
1.7K0
Python 算法基础篇:深度优先搜索( DFS )和广度优先搜索( BFS )
深度优先搜索( DFS )和广度优先搜索( BFS )是两种常用的图遍历算法,用于在图中搜索目标节点或遍历图的所有节点。本篇博客将介绍 DFS 和 BFS 算法的基本概念,并通过实例代码演示它们的应用。
小蓝枣
2023/07/22
3.2K0
DFS深度优先搜索解决迷宫问题
的网格迷宫G。G的每个格子要么是道路,要么是障碍物(道路用1表示,障碍物用2表示)。
别团等shy哥发育
2023/03/14
9240
DFS深度优先搜索解决迷宫问题
深度优先算法和广度优先算法
在数据结构中,树和图可以说是不可或缺的两种数据结构。其中,对于图来说,最重要的算法可以说就是遍历算法。而搜索算法中,最标志性的就是深度优先算法和广度优先算法。
跋扈洋
2022/12/03
1K0
无向图----广度优先搜索
上一篇:无向图的实现 下一篇:深度优先遍历 广度优先搜索比深度优先搜索更容易解决最短路径问题。 使用广度优先搜索查找图中路径: public class BreadthFirstPaths { private boolean[] marked; private int[] edgeTo; private int s; public BreadthFirstPaths(Graph G,int s){ marked = new boolean[G.V()]; edgeTo = new int
SuperHeroes
2018/05/30
1.1K0
《算法图解》note 6 图以及广度优先搜索和深度优先搜索1.图2.广度优先搜索3.深度优先搜索
这是《算法图解》第六篇读书笔记,涉及的主要内容为图结构、深度优先搜索和广度优先搜索。 1.图 1.1图的概述 图(graph)是一种基本的数据结构,它由点和边构成。 根据边有无指向性,可将图分为有向图、无向图。这两种图分别表明点与点之间的关系是单向的(有向图)还是过双向的(无向图)。 1.2图的用途 图可用于表示物体之间的关系,以及用于查找两地点之间的最短路径等。 1.3图的存储结构(python实现有向图) 图的存储结结构可分为邻接矩阵和邻接列表。 下文将按下图展示邻接矩阵和邻接表。 先约定三点:
billyang916
2018/06/04
1.1K0
七十九、深度和广度优先搜索算法
深度优先算法的本质是回溯算法,多数是应用在树上,一个比较典型的应用就是二叉树的中序遍历。
润森
2022/08/17
6250
BFS(Breath First Search 广度优先搜索)
==说明==:Node节点class对象代码请查看上一篇文章,https://blog.csdn.net/a924382407/article/details/118394577
刘大猫
2024/11/01
990
深度优先搜索与广度优先搜索的探索之路
在数据结构和算法的世界中,深度优先搜索(DFS)和广度优先搜索(BFS)是两种基本且常用的图遍历算法。它们在解决许多实际问题中扮演着重要角色。本文旨在深入探讨这两种算法的原理,并分析它们之间的区别。
运维开发王义杰
2023/10/23
3620
深度优先搜索与广度优先搜索的探索之路
常用的搜索算法之深度优先搜索
深度优先搜索(DFS)是一种用于遍历或搜索树或图的算法。该算法会尽可能深地搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这个过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
jack.yang
2025/04/05
1530
深度优先搜索遍历与广度优先搜索遍历
1、图的遍历      和树的遍历类似,图的遍历也是从某个顶点出发,沿着某条搜索路径对图中每个顶点各做一次且仅做一次访问。它是许多图的算法的基础。      深度优先遍历和广度优先遍历是最为重要的两种遍历图的方法。它们对无向图和有向图均适用。   注意:     以下假定遍历过程中访问顶点的操作是简单地输出顶点。 2、布尔向量visited[0..n-1]的设置      图中任一顶点都可能和其它顶点相邻接。在访问了某顶点之后,又可能顺着某条回路又回到了该顶点。为了避免重复访问同一个顶点,必须记住每个已访问的顶点。为此,可设一布尔向量visited[0..n-1],其初值为假,一旦访问了顶点Vi之后,便将visited[i]置为真。 深度优先遍历(Depth-First Traversal) 1.图的深度优先遍历的递归定义      假设给定图G的初态是所有顶点均未曾访问过。在G中任选一顶点v为初始出发点(源点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过;然后依次从v出发搜索v的每个邻接点w。若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止。      图的深度优先遍历类似于树的前序遍历。采用的搜索方法的特点是尽可能先对纵深方向进行搜索。这种搜索方法称为深度优先搜索(Depth-First Search)。相应地,用此方法遍历图就很自然地称之为图的深度优先遍历。 2、深度优先搜索的过程      设x是当前被访问顶点,在对x做过访问标记后,选择一条从x出发的未检测过的边(x,y)。若发现顶点y已访问过,则重新选择另一条从x出发的未检测过的边,否则沿边(x,y)到达未曾访问过的y,对y访问并将其标记为已访问过;然后从y开始搜索,直到搜索完从y出发的所有路径,即访问完所有从y出发可达的顶点之后,才回溯到顶点x,并且再选择一条从x出发的未检测过的边。上述过程直至从x出发的所有边都已检测过为止。此时,若x不是源点,则回溯到在x之前被访问过的顶点;否则图中所有和源点有路径相通的顶点(即从源点可达的所有顶点)都已被访问过,若图G是连通图,则遍历过程结束,否则继续选择一个尚未被访问的顶点作为新源点,进行新的搜索过程。 3、深度优先遍历的递归算法 (1)深度优先遍历算法   typedef enum{FALSE,TRUE}Boolean;//FALSE为0,TRUE为1   Boolean visited[MaxVertexNum]; //访问标志向量是全局量   void DFSTraverse(ALGraph *G)   { //深度优先遍历以邻接表表示的图G,而以邻接矩阵表示G时,算法完全与此相同     int i;     for(i=0;i<G->n;i++)       visited[i]=FALSE; //标志向量初始化     for(i=0;i<G->n;i++)       if(!visited[i]) //vi未访问过         DFS(G,i); //以vi为源点开始DFS搜索    }//DFSTraverse (2)邻接表表示的深度优先搜索算法   void DFS(ALGraph *G,int i){     //以vi为出发点对邻接表表示的图G进行深度优先搜索     EdgeNode *p;     printf("visit vertex:%c",G->adjlist[i].vertex);//访问顶点vi     visited[i]=TRUE; //标记vi已访问     p=G->adjlist[i].firstedge; //取vi边表的头指针     while(p){//依次搜索vi的邻接点vj,这里j=p->adjvex       if (!visited[p->adjvex])//若vi尚未被访问         DFS(G,p->adjvex);//则以Vj为出发点向纵深搜索       p=p->next; //找vi的下一邻接点      }    }//DFS (3)邻接矩阵表示的深度优先搜索算法   void DFSM(MGraph *G,int i)   { //以vi为出发点对邻接矩阵表示的图G进行DFS搜索,设邻接矩阵是0,l矩阵     int j;     printf("visit vertex:%c",G->vexs[i]);//访问顶点vi     visited[i]=TRUE;     for(j=0;j<G->n;j++) //依次搜索vi的邻接点       if(G->edges[i][j]==1&&!vi
阳光岛主
2019/02/19
2.4K0
动画演示广度优先算法寻找最短路径
上一节,我们刚刚介绍了使用深度优先算法(DFS)解决迷宫问题,这一节我们来介绍广度优先算法(BFS)。BFS 算法与 DFS 十分相似,唯一的区别就是 DFS 算法使用后进先出的栈来保存节点,而 BFS 算法使用先进先出的队列来存储节点,除此之外简直就是一母同胞的亲兄弟。当然,这两种方案各有千秋。DFS 算法找到的路径往往不是最短路径,速度慢但占用内存较少,而 BFS 算法找到的总是最短路径,速度较快但占用内存较多。
用户2870857
2019/12/23
2.2K0
动画演示广度优先算法寻找最短路径
图论基础及深度优先遍历(DFS)、广度优先遍历(BFS)
无论是数据中心内的整网网络拓扑,还是网络设备内的业务转发逻辑(如开源用户态网络协议栈 VPP:Vector Packet Processing)都构成一张有向图。想要从这张图中提取有用信息,就需要图论方面的相关知识。
Flowlet
2024/04/19
4.9K0
图论基础及深度优先遍历(DFS)、广度优先遍历(BFS)
算法系列之搜索算法-广度优先搜索BFS
随着每年"金三银四"招聘季的到来,许多求职者开始积极备战面试。在众多面试环节中,机试往往是不可或缺的一环,而算法能力更是机试考核的重点。为此,我们特别推出算法系列文章,帮助大家系统复习算法知识。今天,我们将首先探讨搜索算法中的重要内容——广度优先搜索(BFS)。
修己xj
2025/03/12
1700
算法系列之搜索算法-广度优先搜索BFS
迷宫问题(maze problem)——深度优先(DFS)与广度优先搜索(BFS)求解
给定一个迷宫,指明起点和终点,找出从起点出发到终点的有效可行路径,就是迷宫问题(maze problem)。
恋喵大鲤鱼
2018/08/03
13.8K1
迷宫问题(maze problem)——深度优先(DFS)与广度优先搜索(BFS)求解
数据结构与算法——BFS(广度优先搜索)
广度优先搜索(Breadth-First Search,简称BFS)是一种遍历或搜索树和图的算法,也称为宽度优先搜索,BFS算法从图的某个节点开始,依次对其所有相邻节点进行探索和遍历,然后再对这些相邻节点的相邻节点进行探索,直到遍历完所有的节点。BFS算法使用队列来辅助实现,将起始节点放入队列中,然后依次取出队列中的节点,访问其相邻节点,并将其加入队列。这样可以保证从起始节点出发,依次按照距离顺序遍历节点。BFS常用于寻找最短路径,因为它按照从起点到每个节点的距离来探索节点。
摆烂小白敲代码
2024/09/23
3650
数据结构与算法——BFS(广度优先搜索)
算法:队列与广度优先搜索(迷宫问题)
本文介绍了迷宫问题以及广度优先搜索算法。迷宫由一个二维矩阵表示,其中0表示可以通行的道路,1表示墙壁。起点和终点用两个坐标列表表示。广度优先搜索从起点开始,每次从当前道路的所有可行走法中选择一个未探索过的方向进行探索,直到找到终点或者无法继续探索为止。相比深度优先搜索,广度优先搜索在求解迷宫问题时可以更快地找到最短路径,但需要更多的内存空间。
s1mba
2018/01/03
1.5K0
算法:队列与广度优先搜索(迷宫问题)
推荐阅读
相关推荐
算法系列之深度/广度优先搜索解决水桶分水的最优解及全部解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档