算法謎題1,狼羊菜過河

問題描述

農夫須要把狼、羊、菜和本身運到河對岸去,只有農夫可以划船,並且船比較小,除農夫以外每次只能運一種東西,還有一個棘手問題,就是若是沒有農夫看着,羊會偷吃菜,狼會吃羊。請考慮一種方法,讓農夫可以安全地安排這些東西和他本身過河。c#

分析

問題很簡單,但如何用計算機求解呢。安全

農夫渡河從本質上是一種狀態的改變。this

有農夫、狼、羊、菜四個個體,任什麼時候刻每一個個體的狀態只有一種,每一個個體有兩種狀態(沒有過河、已通過河)。3d

依次用4位分別表明農夫、狼、羊、菜,0表示未過河,1表示已過河。則起始狀態爲0000,目標狀態爲1111。code

共有8種過河動做(狀態轉換運算)

  • 農夫單獨過河
  • 農夫帶狼過河
  • 農夫帶羊過河
  • 農夫帶菜過河
  • 農夫單獨返回
  • 農夫帶狼返回
  • 農夫帶羊返回
  • 農夫帶菜返回

優先級:
農夫過河時,優先帶貨物;回返時優先不帶貨物。blog

有限種狀態:

可能有16(2^4)種狀態,但由於狼吃羊,羊吃菜的限制,部分狀態是沒法成立的。ip

實現

狀態空間樹(回溯法)

是以0000爲根的一顆狀態樹,當某個葉子節點是狀態1111,則表示從根到這個葉子節點之間的狀態序列是本問題的一個解,須要避免出現重複狀態致使死循環。
方法1: 每一個狀態有8種可選動做,轉換爲8個新狀態,但在特定狀態下某些動做是無效的。get

定義8種狀態轉換運算,對當前節點遍歷執行這8種運算,找到全部子節點string

方法2: 依據當前狀態,判別它全部可選的動做(最多4種)。it

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}";
        }
    } 
}

相關文章
相關標籤/搜索