zoukankan      html  css  js  c++  java
  • 【数据结构与算法】狼、羊、菜和农夫过河:使用图的广度优先遍历实现

    【数据结构与算法】狼、羊、菜和农夫过河:使用图的广度优先遍历实现

    Java
    农夫需要把狼、羊、菜和自己运到河对岸去,只有农夫能够划船,而且船比较小。除农夫之外每次只能运一种东西。还有一个棘手问题,就是如果没有农夫看着,羊会偷吃菜,狼会吃羊。请考虑一种方法,让农夫能够安全地安排这些东西和他自己过河。

    解题思路

    学了图论的广度优先遍历算法后,我们可以使用广度优先遍历的思想来完成这道题。
    首先定义如何表达农夫、狼、羊、菜在河的哪一边。只有两种状态:

    1. 在河的一边(假设为东边)
    2. 在河的另一边(假设为西边)
      那么恰好可以用0和1来表达,任务定义如下(使用字符串来表达):
        //      人  狼  羊  菜
        //  源: 0   0   0   0
        //目标: 1   1   1   1
        String s = "0000";
        String t = "1111";
    

    那接下来程序的任务就是搜索出从st的过程了。那么如何转换成图论问题?
    我们知道,0000 代表农夫、狼、羊、菜都在河的东边,那么下一种状态可以有如下几种选择:

    1. 东:空狼羊菜 | 西:人空空空(农夫自己过河)
    2. 东:空空羊菜 | 西:人狼空空(农夫带狼过河)
    3. 东:空狼空菜 | 西:人空羊空(农夫带羊过河)
    4. 东:空狼羊空 | 西:人空空菜(农夫带菜过河)

    我们根据这个可以绘制一个图,顶点0000 分别与顶点1000、顶点1100、顶点1010、顶点1001有边连接;

    其中,根据规则在没有农夫的情况下,狼和羊不能在一起,羊和菜不能在一起,所以排除掉以上的1,2,4选项。那么下一个状态就是 0101
    然后根据这个原理,再往下查找有哪些是可以的:

    1. 东:人狼空菜 | 西:空空羊空(农夫自己过河)
    2. 东:人狼羊菜 | 西:空空空空(农夫带羊过河)

    我们根据这个也可以绘制一个图,顶点0101 分别与顶点0000、顶点0010有边连接;

    然后再根据规则进行查找。

    那么我们可以写出下一个状态的算法:

    private HashMap<String, String> getNextSta(String sta) {
        HashMap<String, String> nextSta = new HashMap<>();
        char[] chars = sta.toCharArray();
        char backup;
        String n;
        if (chars[0] == '1') {//在1这一侧(东)
            chars[0] = '0'; // 农夫从1到0这一侧
            n = new String(chars);
            if (!isFailed(n)) {
                nextSta.put(n, "农夫从东侧到西侧");
            }
            //------------------------
            backup = chars[1]; // 备份
            if (chars[1] == '1') { // 如果狼在这边
                chars[1] = '0'; // 带狼过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从东侧带狼到西侧");
                }
            }
            chars[1] = backup; // 恢复
            //------------------------
            backup = chars[2]; // 备份
            if (chars[2] == '1') { // 如果羊在这边
                chars[2] = '0'; // 带羊过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从东侧带羊到西侧");
                }
            }
            chars[2] = backup;// 恢复
            //------------------------
            backup = chars[3];// 备份
            if (chars[3] == '1') { // 如果菜在这边
                chars[3] = '0'; // 带菜过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从东侧带菜到西侧");
                }
            }
            chars[3] = backup;// 恢复
        } else if (chars[0] == '0') {
            chars[0] = '1'; // 农夫从0到1这一侧
            n = new String(chars);
            if (!isFailed(n)) {
                nextSta.put(n, "农夫从西侧到东侧");
            }
            //------------------------
            backup = chars[1]; // 备份
            if (chars[1] == '0') { // 如果狼在这边
                chars[1] = '1'; // 带狼过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从西侧带狼到东侧");
                }
            }
            chars[1] = backup; // 恢复
            //------------------------
            backup = chars[2]; // 备份
            if (chars[2] == '0') { // 如果羊在这边
                chars[2] = '1'; // 带羊过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从西侧带羊到东侧");
                }
            }
            chars[2] = backup;// 恢复
            //------------------------
            backup = chars[3];// 备份
            if (chars[3] == '0') { // 如果菜在这边
                chars[3] = '1'; // 带菜过去
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从西侧带菜到东侧");
                }
            }
            chars[3] = backup;// 恢复
        }
        return nextSta;
    }
    
    

    写出失败的情况(即不可出现的情况)判断算法:

        private boolean isFailed(String sta) {
            char[] part = sta.toCharArray();
            if (part[0] == '0') {
                if (part[1] == '1' && part[2] == '1') { // 狼和羊,没有农夫
                    return true;
                }
                if (part[2] == '1' && part[3] == '1') { // 菜和羊,没有农夫
                    return true;
                }
            } else if (part[0] == '1') {
                if (part[1] == '0' && part[2] == '0') { // 狼和羊,没有农夫
                    return true;
                }
                if (part[2] == '0' && part[3] == '0') { // 菜和羊,没有农夫
                    return true;
                }
            }
        }
    

    接下来就是使用图论的广度优先遍历来搜索出最短路径了
    使用两个映射来存储遍历路径和描述

    HashMap<String, String> pre; // 记录
    HashMap<String, String> des; // 描述
    

    针对状态进行广度优先遍历

    ArrayList<String> queue = new ArrayList<>();
    
    if (isFailed(s)) {
        return;
    }
    
    String sta = s;
    queue.add(sta);
    pre.put(sta, sta);
    while (!queue.isEmpty()) {
        sta = queue.remove(0);
    
        HashMap<String, String> nextSta = getNextSta(sta);
    
        for (String curSta : nextSta.keySet()) {
            if (!pre.containsKey(curSta)) { // 如果还没访问过
                queue.add(curSta);
                pre.put(curSta, sta); // 记录父顶点
                des.put(sta + curSta, nextSta.get(curSta));  // 记录过程描述
                if (curSta.equals(t)) {
                    return;
                }
            }
        }
    }
    

    执行完这个后,就能在pre里找到完成任务的路径了。

    最后这里就是整理和输出了

    
        public Iterable<String> process() {
            ArrayList<String> res = new ArrayList<>();
            if (!pre.containsKey(t)) {
                return res;
            }
            String cur = t;
            while (!cur.equals(s)) {
                res.add(cur);
                cur = pre.get(cur);
            }
            res.add(s);
            Collections.reverse(res);
    
            ArrayList<String> ret = new ArrayList<>();
            String p = res.get(0);
            for (int i = 1; i < res.size(); i++) {
                ret.add("状态 : " + getStaDes(p));
                ret.add("步骤 : " + i);
                String s = res.get(i);
                ret.add("操作 : " + des.get(p + s));
                p = s;
            }
            ret.add("状态 : " + getStaDes(p));
            return ret;
        }
    
        private String getStaDes(String sta) {
            char[] s = sta.toCharArray();
            char[] dc = new char[6], xc = new char[6];
            ArrayList<Character> d = new ArrayList<>(), x = new ArrayList<>(), c = null;
            for (int i = 0; i < s.length; i++) {
                if (s[i] == '1') {
                    c = d;
                } else {
                    c = x;
                }
    
                if (i == 0) {
                    c.add('人');
                } else if (i == 1) {
                    c.add('狼');
                } else if (i == 2) {
                    c.add('羊');
                } else if (i == 3) {
                    c.add('菜');
                }
            }
    
            dc[0] = '东';
            xc[0] = '西';
            dc[1] = ':';
            xc[1] = ':';
    
            for (int i = 2; i < dc.length; i++) {
                dc[i] = '空';
            }
            for (int i = 2; i < xc.length; i++) {
                xc[i] = '空';
            }
    
            for (int i = 0; i < d.size(); i++) {
                dc[i + 2] = d.get(i);
            }
            for (int i = 0; i < x.size(); i++) {
                xc[i + 2] = x.get(i);
            }
    
            return new String(xc) + " | " + new String(dc);
        }
    
    

    然后主函数运行调用一下:

        public static void main(String[] args) {
            FarmerTransport ft = new FarmerTransport();
            for (String s : ft.process()) {
                System.out.println(s);
            }
        }
    

    运行结果如下:

    "C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.5\jbr\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.5\lib\idea_rt.jar=14007:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.5\bin" -Dfile.encoding=UTF-8 -classpath D:\Project\DataStructureJavaLearn2021\out\production\data_structure_java top.minuy.subject.costom.bfs.FarmerTransport
    状态 : 西:人狼羊菜 | 东:空空空空
    步骤 : 1
    操作 : 农夫从西侧带羊到东侧
    状态 : 西:狼菜空空 | 东:人羊空空
    步骤 : 2
    操作 : 农夫从东侧到西侧
    状态 : 西:人狼菜空 | 东:羊空空空
    步骤 : 3
    操作 : 农夫从西侧带狼到东侧
    状态 : 西:菜空空空 | 东:人狼羊空
    步骤 : 4
    操作 : 农夫从东侧带羊到西侧
    状态 : 西:人羊菜空 | 东:狼空空空
    步骤 : 5
    操作 : 农夫从西侧带菜到东侧
    状态 : 西:羊空空空 | 东:人狼菜空
    步骤 : 6
    操作 : 农夫从东侧到西侧
    状态 : 西:人羊空空 | 东:狼菜空空
    步骤 : 7
    操作 : 农夫从西侧带羊到东侧
    状态 : 西:空空空空 | 东:人狼羊菜
    
    Process finished with exit code 0
    
    

    成功使用图论的广度优先遍历的思想解出本题~

    代码

    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    
    /**
     * 一道智力题
     * 农夫需要把狼、羊、菜和自己运到河对岸去,
     * 只有农夫能够划船,而且船比较小。
     * 除农夫之外每次只能运一种东西,
     * 还有一个棘手问题,就是如果没有农夫看着:
     * 羊会偷吃菜,狼会吃羊。
     * 请考虑一种方法,
     * 让农夫能够安全地安排这些东西和他自己过河。
     *
     * @author Minuy
     * @time 8:57
     * @date 2021/11/21
     */
    public class FarmerTransport {
        //       人  狼  羊  菜
        //  源: 0   0   0   0
        //目标: 1   1   1   1
    
        String s = "0000";
        String t = "1111";
        HashMap<String, String> pre; // 记录
        HashMap<String, String> des; // 描述
    
        public FarmerTransport() {
            pre = new HashMap<>();
            des = new HashMap<>();
    
            ArrayList<String> queue = new ArrayList<>();
    
            if (isFailed(s)) {
                return;
            }
    
            String sta = s;
            queue.add(sta);
            pre.put(sta, sta);
            while (!queue.isEmpty()) {
                sta = queue.remove(0);
    
                HashMap<String, String> nextSta = getNextSta(sta);
    
                for (String curSta : nextSta.keySet()) {
                    if (!pre.containsKey(curSta)) { // 如果还没访问过
                        queue.add(curSta);
                        pre.put(curSta, sta); // 记录父顶点
                        des.put(sta + curSta, nextSta.get(curSta));  // 记录过程描述
                        if (curSta.equals(t)) {
                            return;
                        }
                    }
                }
            }
        }
    
        private HashMap<String, String> getNextSta(String sta) {
            HashMap<String, String> nextSta = new HashMap<>();
            char[] chars = sta.toCharArray();
            char backup;
            String n;
            if (chars[0] == '1') {//在1这一侧(东)
                chars[0] = '0'; // 农夫从1到0这一侧
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从东侧到西侧");
                }
                //------------------------
                backup = chars[1]; // 备份
                if (chars[1] == '1') { // 如果狼在这边
                    chars[1] = '0'; // 带狼过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从东侧带狼到西侧");
                    }
                }
                chars[1] = backup; // 恢复
                //------------------------
                backup = chars[2]; // 备份
                if (chars[2] == '1') { // 如果羊在这边
                    chars[2] = '0'; // 带羊过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从东侧带羊到西侧");
                    }
                }
                chars[2] = backup;// 恢复
                //------------------------
                backup = chars[3];// 备份
                if (chars[3] == '1') { // 如果菜在这边
                    chars[3] = '0'; // 带菜过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从东侧带菜到西侧");
                    }
                }
                chars[3] = backup;// 恢复
            } else if (chars[0] == '0') {
                chars[0] = '1'; // 农夫从0到1这一侧
                n = new String(chars);
                if (!isFailed(n)) {
                    nextSta.put(n, "农夫从西侧到东侧");
                }
                //------------------------
                backup = chars[1]; // 备份
                if (chars[1] == '0') { // 如果狼在这边
                    chars[1] = '1'; // 带狼过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从西侧带狼到东侧");
                    }
                }
                chars[1] = backup; // 恢复
                //------------------------
                backup = chars[2]; // 备份
                if (chars[2] == '0') { // 如果羊在这边
                    chars[2] = '1'; // 带羊过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从西侧带羊到东侧");
                    }
                }
                chars[2] = backup;// 恢复
                //------------------------
                backup = chars[3];// 备份
                if (chars[3] == '0') { // 如果菜在这边
                    chars[3] = '1'; // 带菜过去
                    n = new String(chars);
                    if (!isFailed(n)) {
                        nextSta.put(n, "农夫从西侧带菜到东侧");
                    }
                }
                chars[3] = backup;// 恢复
            }
            return nextSta;
        }
    
        public Iterable<String> process() {
            ArrayList<String> res = new ArrayList<>();
            if (!pre.containsKey(t)) {
                return res;
            }
            String cur = t;
            while (!cur.equals(s)) {
                res.add(cur);
                cur = pre.get(cur);
            }
            res.add(s);
            Collections.reverse(res);
    
            ArrayList<String> ret = new ArrayList<>();
            String p = res.get(0);
            for (int i = 1; i < res.size(); i++) {
                ret.add("状态 : " + getStaDes(p));
                ret.add("步骤 : " + i);
                String s = res.get(i);
                ret.add("操作 : " + des.get(p + s));
                p = s;
            }
            ret.add("状态 : " + getStaDes(p));
            return ret;
        }
    
        private String getStaDes(String sta) {
            char[] s = sta.toCharArray();
            char[] dc = new char[6], xc = new char[6];
            ArrayList<Character> d = new ArrayList<>(), x = new ArrayList<>(), c = null;
            for (int i = 0; i < s.length; i++) {
                if (s[i] == '1') {
                    c = d;
                } else {
                    c = x;
                }
    
                if (i == 0) {
                    c.add('人');
                } else if (i == 1) {
                    c.add('狼');
                } else if (i == 2) {
                    c.add('羊');
                } else if (i == 3) {
                    c.add('菜');
                }
            }
    
            dc[0] = '东';
            xc[0] = '西';
            dc[1] = ':';
            xc[1] = ':';
    
            for (int i = 2; i < dc.length; i++) {
                dc[i] = '空';
            }
            for (int i = 2; i < xc.length; i++) {
                xc[i] = '空';
            }
    
            for (int i = 0; i < d.size(); i++) {
                dc[i + 2] = d.get(i);
            }
            for (int i = 0; i < x.size(); i++) {
                xc[i + 2] = x.get(i);
            }
    
            return new String(xc) + " | " + new String(dc);
        }
    
        private boolean isFailed(String sta) {
            char[] part = sta.toCharArray();
            if (part[0] == '0') {
                if (part[1] == '1' && part[2] == '1') { // 狼和羊,没有农夫
                    return true;
                }
                if (part[2] == '1' && part[3] == '1') { // 菜和羊,没有农夫
                    return true;
                }
            } else if (part[0] == '1') {
                if (part[1] == '0' && part[2] == '0') { // 狼和羊,没有农夫
                    return true;
                }
                if (part[2] == '0' && part[3] == '0') { // 菜和羊,没有农夫
                    return true;
                }
            }
            return false;
        }
    
        public static void main(String[] args) {
            FarmerTransport ft = new FarmerTransport();
            for (String s : ft.process()) {
                System.out.println(s);
            }
        }
    }
    
    
  • 相关阅读:
    Android屏幕适配全攻略(最权威的官方适配指导)--转
    Android实现全屏的三种方式
    Mysql锁(翻译)
    mysql5.6主从配置
    jvm加载类(更新中)
    如何定位jdk中的native方法源码?
    光学字符识别OCR-2
    光学字符识别OCR
    关于freetype在安装中的遇到的问题
    锚点链接
  • 原文地址:https://www.cnblogs.com/minuy/p/15584016.html
Copyright © 2011-2022 走看看