zoukankan      html  css  js  c++  java
  • 算法谜题1,狼羊菜过河

    问题描述

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

    分析

    问题很简单,但如何用计算机求解呢。

    农夫渡河从本质上是一种状态的改变。

    有农夫、狼、羊、菜四个个体,任何时刻每个个体的状态只有一种,每个个体有两种状态(没有过河、已经过河)。

    依次用4位分别代表农夫、狼、羊、菜,0表示未过河,1表示已过河。则起始状态为0000,目标状态为1111。

    共有8种过河动作(状态转换运算)

    • 农夫单独过河
    • 农夫带狼过河
    • 农夫带羊过河
    • 农夫带菜过河
    • 农夫单独返回
    • 农夫带狼返回
    • 农夫带羊返回
    • 农夫带菜返回

    优先级:
    农夫过河时,优先带货物;回返时优先不带货物。

    有限种状态:

    可能有16(2^4)种状态,但因为狼吃羊,羊吃菜的限制,部分状态是无法成立的。

    实现

    状态空间树(回溯法)

    是以0000为根的一颗状态树,当某个叶子节点是状态1111,则表示从根到这个叶子节点之间的状态序列是本问题的一个解,需要避免出现重复状态导致死循环。
    方法1: 每个状态有8种可选动作,转换为8个新状态,但在特定状态下某些动作是无效的。

    定义8种状态转换运算,对当前节点遍历执行这8种运算,找到所有子节点

    方法2: 依据当前状态,判别它所有可选的动作(最多4种)。

    class Program
    {
        static void Main(string[] args)
        {
            var original = new State();
    
            var path = new List<State>();
            path.Add(original);
            int count = 0;
            Search(path, ref count);
    
            Console.ReadKey();
        }
    
        private static void Search(List<State> path, ref int count)
        {
            var cur = path[path.Count - 1];
    
            if (cur.Man && cur.Wolf && cur.Vegetable && cur.Sheep)
            {
                count++;
                Console.WriteLine($"解{count}:");
                path.ForEach((a) => { Console.WriteLine(a.Action); });
    
                return;
            }
    
            if (cur.Man)
            {
                Switch(path, ref count, cur, "返回");
            }
            else
            {
                Switch(path, ref count, cur, "过河");
            }
        }
    
        private static void Switch(List<State> path, ref int count, State cur, string action)
        {
            var newState = cur.Copy();
            newState.Man = !newState.Man;
    
            newState.Action = "独自" + action;
            Action(path, ref count, newState);
    
            if (cur.Sheep == cur.Man)
            {
                newState.Sheep = !newState.Sheep;
                newState.Action = "带着羊" + action;
                Action(path, ref count, newState);
                newState.Sheep = !newState.Sheep;
            }
    
            if (cur.Wolf == cur.Man)
            {
                newState.Wolf = !newState.Wolf;
                newState.Action = "带着狼" + action;
                Action(path, ref count, newState);
                newState.Wolf = !newState.Wolf;
            }
    
            if (cur.Vegetable == cur.Man)
            {
                newState.Vegetable = !newState.Vegetable;
                newState.Action = "带着菜" + action;
                Action(path, ref count, newState);
                newState.Vegetable = !newState.Vegetable;
            }
        }
    
        private static void Action(List<State> path, ref int count, State newState)
        {
            if (newState.IsOk)
            {
                foreach (var item in path)
                {
                    if (item.Equals(newState))
                    {
                        return;
                    }
                }
    
                path.Add(newState);
                Search(path, ref count);
                path.RemoveAt(path.Count - 1);
            }
        }
    
        //false 表示未过河, true表示已过河
        private class State
        {
            public bool Man { get; set; }
            public bool Wolf { get; set; }
    
            public bool Sheep { get; set; }
    
            public bool Vegetable { get; set; }
    
            public string Action { get; set; }
    
            public bool IsOk
            {
                get
                {
                    if (Wolf == Sheep && Wolf != Man)
                    {
                        return false;
                    }
    
                    if (Sheep == Vegetable && Sheep != Man)
                    {
                        return false;
                    }
    
                    return true;
                }
            }
    
            public State Copy()
            {
                return new State
                {
                    Man = this.Man,
                    Wolf = this.Wolf,
                    Sheep = this.Sheep,
                    Vegetable = this.Vegetable
                };
            }
    
            public bool Equals(State newState)
            {
                return (this.Man == newState.Man
                    && this.Wolf == newState.Wolf
                    && this.Sheep == newState.Sheep
                    && this.Vegetable == newState.Vegetable);
            }
        }
    }
    

    状态空间图

    所有状态作为图的节点

    遍历图,找出所有从0000到1111的路径

    连接状态的条件

    1. 农夫的状态要不一样 (只有农夫可以划船,每次过河,不能缺农夫)
    2. 最多只有一个其他个体的状态不一样(一次只能带一个过河),且这个个体的状态要与农夫一致。

    避免重复

    一个状态只能经过一次。

    class Program
    {
        static void Main(string[] args)
        {
            //找到所有的状态
            var states = new List<Vertex>();
            for (int i = 0; i < 16; i++)
            {
                var temp = i >> 1;
                if (temp == 0b011 || temp == 0b100)
                {
                    continue;
                }
    
                var temp2 = i & 0b1011;
                if (temp2 == 0b1000 || temp2 == 0b0011)
                {
                    continue;
                }
                states.Add(new Vertex { State = i });
            }
    
            var steps = new List<Step>();
            Search(states[0], states, steps);
    
            Console.ReadKey();
        } 
    
        private static void Search(Vertex cur, List<Vertex> states, List<Step> steps)
        {
            if(cur.State == 0b1111)
            {
                Console.WriteLine();
                steps.ForEach((a)=> { Console.WriteLine(a.Description); });
                return;
            }
    
            cur.HasVisited = true;
    
            foreach (var item in states)
            {
                if (!item.HasVisited && CanBeNext(cur.State,item.State))
                {
                    steps.Add(new Step { From = cur.State,To = item.State });
                    Search(item, states, steps);
                    steps.RemoveAt(steps.Count - 1);
                }
            }
    
            cur.HasVisited = false;
        }
        private static bool CanBeNext(int a,int b)
        {
            if (b == 0)
            {
                return false;
            }
    
            if((a^b)>>3 == 0)
            {
                return false;
            }
    
            var man = a >> 3;
    
            var temp = (a & 0b0111)^(b & 0b0111);
            if(temp == 0 || temp == 0b100 && (a & 0b0100)>>2 == man || temp == 0b010 && (a & 0b0010)>>1 == man || temp == 0b001 && (a & 0b0001) == man)
            {
                return true;
            }
    
            return false;
        }
    
        private class Vertex
        {
            public int State { get; set; }
            public bool HasVisited { get; set; }
        }
    
        private class Step
        {
            public int From { get; set; }
            public int To { get; set; }
            public string Description
            {
                get
                {
                    var action = From > 7 ? "返回" : "过河";
                    var temp = (From & 0b0111) ^ (To & 0b0111);
                    if(temp == 0)
                    {
                        action = "独自" + action;
                    }
                    if (temp == 0b100)
                    {
                        action = "带着狼" + action;
                    }
                    if (temp == 0b010)
                    {
                        action = "带着羊" + action;
                    }
                    if (temp == 0b001)
                    {
                        action = "带着菜" + action;
                    }
                    return $"{PrintState(From)}--{action} --> {PrintState(To)}";
                }
            }
            private string PrintState(int a)
            {
                return $"{a / 8}{a % 8 / 4}{a % 4 / 2}{ a % 2}";
            }
        } 
    }
    

  • 相关阅读:
    世界史上最著名的十大思想实验
    excel 鼠标上下左右移动
    excel 批注
    C# TextWriter类
    C#中的Hashtable
    cython安装、使用
    Java JsonPath grab InvalidPathException in code, you must be catching Java 7's java.nio.file.InvalidPathException instead of JsonPath's com.jayway.jsonpath.InvalidPathExceptio
    Java牛角尖【007】:Java中的Error能不能被Catch
    2016亚洲城市GDP50强出炉
    全国5A级旅游景区已达250家
  • 原文地址:https://www.cnblogs.com/wj033/p/9129984.html
Copyright © 2011-2022 走看看