C#实现野人与传教士过河动画演示

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36260974/article/details/84189525

野人与传教士过河问题

问题重述:
有三个传教士和三个野人过河, 只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于传教士的人数,那么传教士就会有危险,采用何种渡河方法,可以安全过河。

算法分析:
初始状态:左岸,3野人,3传教士;右岸, 0野人,0传教士;船停在左岸,船上有0个人。
目标状态:左岸,0野人,0传教士;右岸, 3野人,3传教士;船停在右岸,船上有0个人。
将整个问题抽象成怎样从初始状态经一系列的中间状态从而达到目标状态,状态的改变是通过划船渡河来引发的。
根据要求,共得出以下5中可能的渡河方案:
(1)渡2传教士
(2)渡2野人
(3)渡1野人1传教士
(4)渡1传教士
(5)渡1野人
本程序使用类来定义状态结点,使用集合存储状态结点,使用递归的思想来寻找目标状态。

程序详细执行流程如下:
首先,包含状态(首次为初始状态)的结构体结点(已存入存储结构)传入处理函数,然后判断该传入结点状态是否为目标状态,是则遍历打印数据存储结构(集合),打印完成之后,返回递归调用处,顺序执行之后代码(此步骤关系到是否能找到所有过河路径);否则继续判断是否该传入结点已存在于存储结构当中(集合),如存在,不再往下执行,返回递归调用处,顺序执行之后代码;若不存在,则继续判断该传入状态的人数是否合理(是否出现人物数量小于0的情况等),若不合理,返回递归调用处,顺序执行之后代码;若合理,则继续判断传教士和野人人数限制条件,即在传教士人数不为0的情况下,野人人数是否大于传教士人数,若大于则出现吃人的情况,也就是说该传入状态也不合理,则返回递归调用处,顺序执行之后代码;若不满足大于条件,则说明该状态是路径转态,也就是合理的,那么进行五种渡河方案的依次变换,首先为第一种渡河方案,两个传教士过河(注意:此处的5中渡河方案没有固定顺序,也可以是其他渡河方案),那么对该传入状态的左岸和右岸的传教士人数和野人人数进行增减(若为左岸到右岸,则左岸人数减,右岸人数加,此处有一个小技巧见本段末尾)。增减完成并改变船的状态(使用正负一表示,正一为左岸,负一为右岸)以后就产生了一个新的状态,将该状态存入存储结构(集合),之后此处又递归调用处理函数,将新产生的转态结点传入,再次进行上述条件限制判断。若在该判断途中被返回至递归调用处,说明该状态不合理,则此时将已经存入存储结构的状态结点移出存储结构,然后程序顺序执行,进行下一个渡河方案的处理,也就是说,此时的处理是对上一个传入结点的操作(因为刚传入的已经移出了);若在判断途中未被返回至递归调用处,也就是说,传入的结点合理了,那么又开始从第一种渡河方案开始对该传入状态进行操作。按照上述过程循环执行,直到出现目标状态,回到本段开头,遍历存储结构,打印渡河路径结点信息。完成以后,返回递归调用处,顺序执行之后代码,此后的操作是在寻找其他渡河路径。原理为:由于该处理函数末尾存在return语句(关键),所以在找到目标状态并返回之后,目标转态结点同样会被移出存储结构,然后在其上一个结点开始顺序往下执行操作之后的一种渡河方案,查看是否在该结点处,还有其他渡河方案可以达到目标状态,若有则同样按上述方法执行(打印输出),若执行完后面的所有渡河方案,发现都没有能够达到目标状态的结点,则会执行末尾的返回语句,返回之后,该状态结点也会被移除(关键),那么此时操作的状态结点就是上上个结点状态,对其进行其后的渡河方案操作。按照此法,不断往后退,直到所有结点都被移除,此时说明已经完成所有渡河路径的搜索。至此,本程序的执行过程叙述完毕。

小技巧:从左岸到右岸,和从右岸到左岸的状态变化是不一样的,前者左岸的人数减,右岸的人数加;后者左岸的人数加,右岸的人数减。我们不应单独再写程序来处理,而是应该使用船的转态带入计算来处理,注意,此技巧在于船的转态使用正负一来表示,而不应该是1和0,以及其他表示方法。为什么这么说?因为任何数乘以一,其本身都不会改变(有我也不会承认)。而正负号在此起到关键作用,我们使用正负一去乘以五种渡河方案的改变数值,从而得到的就是我们变换的正确结果,不论左岸右岸,都是正确合理的。

动画实现关键:
(1)在此,对于C#在动画实现部分进行一些说明总结。初期,我是想通过在for循环打印信息的时候实现按钮的隐藏和显示来实现动画演示的,但是事实上并不是我想象的那么简单的,通过许多方法实践后,发现在for循环里面无法对按钮的能否可见进行操作,而需要等待for循环完成以后,设定的部分按钮才会显示出来。这也就是说不能通过该方法来实现动画演示了。后来我想到直接移动按钮的方法,发现该方法可以在for循环当中进行操作,于是,实现了动画演示的第一步。但是问题在于,我怎么控制那个按钮跳过河(左移),跳过河的按钮不能继续往前跳(左移),而只能往回跳(右移),但是指定按钮来执行跳过河,这样就会出现一个问题,例如,第一次,指定野人按钮1,和野人按钮2跳过河,之后,那个按钮跳回来呢?(程序上实现不了)就算强行指定一个,跳回来以后,我需要再次执行两个野人按钮跳过河,同样是执行刚才的代码,但是,刚才是指定的是野人按钮1,和野人按钮2,而现在应该是回来的野人按钮和野人按钮3过河。所以在程序上如果指定那个人物按钮跳过河,是实现不了动画演示的。
(2)现在,介绍程序是如何解决上述问题的。首先,我将所有状态按钮分左岸传教士和野人,右岸传教士和野人共计四组,分别添加进四个集合,表示不同岸的不同人物。 将左岸的集合大小直接复制给变量Ly(左岸的野人)和Lc(左岸的传教士),而将右岸的集合大小减4(3-4)以后赋值给Ry(右岸的野人)和Rc(右岸的传教士),也可以直接复制-1。这种思想类似于存在一个指向(最近写程序频繁受益于该思想),该指向在初始下指向左岸集合的末尾元素,而右岸没有人,所以指向-1,当发生过河事件的时候,利用Ly、Lc、Ry、Rc来改变指向位置,例如在初始状态下,过河连个野人,按照程序当中设计的结构,Ly会被连续两次执行减一操作,二Ry会连续两次执行加一操作。通过这种思想,我只用判断岸上是否有人存在,并且按照已经产生的正确的过程状态来进行按钮移动,也就不会出错,详见程序(表达能力欠缺,可能未表达出正确思想,可参照程序理解)。

动画演示关键程序(此处只演示左岸到右岸部分,详见完整程序):

//左岸到右岸
if (list[i].getBoat_location() == 1)
{    
    cc.Left += 300;                                       
    if (Math.Abs(list[i+1].getLeft_c()-list[i].getLeft_c()) >= 1)
    {                         
        Lc--;
        Rc++;
        Left_c[Lc].Left += 300;                       
        if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) >= 2)
        {
            Lc--;
            Rc++;
            Left_c[Lc].Left += 300;                               
            if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) == 3)
            {
                Lc--;
                Rc++;
                Left_c[Lc].Left += 300;                   
            }
        }
    }
    if (Math.Abs(list[i+1].getLeft_y()-list[i].getLeft_y()) >= 1)
    {
        Ly--;
        Ry++;                          
        Left_y[Ly].Left += 300;                                                     
        if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) >= 2)
        {
            Ly--;
            Ry++;
            Left_y[Ly].Left += 300;          
            if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) == 3)
            {
                Ly--;
                Ry++;
                Left_y[Ly].Left += 300;                                   
            }
        }
    }
}

程序执行效果图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完整代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//定义过河状态类
namespace C_Y_H
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();         
        }
       List<RiverSide> list = new List<RiverSide>();
       List<Control> Left_c = new List<Control>();
       List<Control> Left_y = new List<Control>();
       List<Control> Right_c = new List<Control>();
       List<Control> Right_y = new List<Control>();
       int  passNum = 0;
       int Ly;
       int Ry;
       int Lc;
       int Rc;          
        public int handle(RiverSide boat)
        {          
            //是否达到目标转态
           if (boat.getRight_c() == 3 && boat.getRight_y() == 3) 
		    {             
			    passNum++;
                textBox1.AppendText("\n\n已为您找到第" + passNum + "条路径!\n\n");
                textBox1.AppendText("\n\n左传" + "\t" + "左野" + "\t" + "右传" + "\t" + "右野" + "\t" + "船\n\n");
                Ly = Left_y.Count;         
                Ry = -1;
                Lc = Left_c.Count;
                Rc = -1;
               if(passNum != 1)
               {
                   button9.Left -= 300;
                   button10.Left -= 300;
                   button11.Left -= 300;
                   button12.Left -= 300;
                   button13.Left -= 300;
                   button14.Left -= 300;
                   cc.Left -= 300;
               }               
                for ( int i = 0; i < list.Count; i++)
                {
                    System.Threading.Thread.Sleep(1000);  
                    textBox1.AppendText(" " + list[i].getLeft_c() + "\t " + list[i].getLeft_y() + "\t " + list[i].getRight_c() + "\t " + list[i].getRight_y() + "\t" + list[i].getBoat_location()+"\n");
                    if (list[i].getBoat_location() == 1)
                    {    
                        cc.Left += 300;                                       
                        if (Math.Abs(list[i+1].getLeft_c()-list[i].getLeft_c()) >= 1)
                        {                         
                            Lc--;
                            Rc++;
                            Left_c[Lc].Left += 300;                       
                            if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) >= 2)
                            {
                                Lc--;
                                Rc++;
                                Left_c[Lc].Left += 300;
                               
                                if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) == 3)
                                {
                                    Lc--;
                                    Rc++;
                                    Left_c[Lc].Left += 300;                                       
                                }
                            }
                        }
                        if (Math.Abs(list[i+1].getLeft_y()-list[i].getLeft_y()) >= 1)
                        {
                            Ly--;
                            Ry++;                          
                            Left_y[Ly].Left += 300;                                                   
                            if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) >= 2)
                            {
                                Ly--;
                                Ry++;
                                Left_y[Ly].Left += 300;                               
                                if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) == 3)
                                {
                                    Ly--;
                                    Ry++;
                                    Left_y[Ly].Left += 300;                                   
                                }
                            }
                        }
                    }
                    if (list[i].getBoat_location() == -1)
                    {                      
                        if (i != list.Count - 1)
                        {
                            cc.Left -= 300;
                            if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) >= 1)
                            {                              
                                Left_c[Lc].Left -= 300;
                                Lc++;
                                Rc--;
                                if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) >= 2)
                                {
                                    Left_c[Lc].Left -= 300;
                                    Lc++;
                                    Rc--;
                                    if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) == 3)
                                    {
                                        Left_c[Lc].Left -= 300;
                                        Lc++;
                                        Rc--;
                                    }
                                }
                            }
                            if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) >= 1)
                            {
                                
                                Right_y[Ry].Left -= 300;
                                Ry--;
                                Ly++;
                                if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) >= 2)
                                {
                                    Right_y[Ry].Left -= 300;
                                    Ry--;
                                    Ly++;
                                    if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) == 3)
                                    {
                                        Right_y[Ry].Left -= 300;
                                        Ry--;
                                        Ly++;
                                    }
                                }
                            }
                        }
                    }
                    System.Threading.Thread.Sleep(200);  
                 }              
			return 0;
		    }
           //是否重复操作
           for (int i = 0; i < list.Count -1; i++)
           {
               if (boat.getLeft_c() == list[i].getLeft_c() && boat.getLeft_y() == list[i].getLeft_y()) 
			    {
                    if (boat.getBoat_location() == list[i].getBoat_location())
				    {
					    return 0;
				    }
			    }
           }
           // 人数合理吗
           if (boat.getLeft_c() < 0 || boat.getLeft_y() < 0 || boat.getRight_c() < 0 || boat.getRight_y() < 0)
           {
               return 0;
           }
           // 传教士被吃了没
           if ((boat.getLeft_c() < boat.getLeft_y() && boat.getLeft_c() != 0) || (boat.getRight_c() < boat.getRight_y() && boat.getRight_c() != 0))
           {
               return 0;
           }
           //定义一个空的状态
           RiverSide nullboat = new RiverSide();

           // 两个传教士过河
           nullboat.setLeft_c(boat.getLeft_c() - 2 * boat.getBoat_location());
           nullboat.setLeft_y(boat.getLeft_y());
           nullboat.setRight_c(boat.getRight_c() + 2 * boat.getBoat_location());
           nullboat.setRight_y(boat.getRight_y());
           nullboat.setBoat_location(-boat.getBoat_location());
           list.Add(nullboat);
           handle(nullboat);
           list.RemoveAt(list.Count-1);

           // 两个野人过河
           nullboat.setLeft_c(boat.getLeft_c());
           nullboat.setLeft_y(boat.getLeft_y() - 2 * boat.getBoat_location());
           nullboat.setRight_c(boat.getRight_c());
           nullboat.setRight_y(boat.getRight_y() + 2 * boat.getBoat_location());
           nullboat.setBoat_location(-boat.getBoat_location());
           list.Add(nullboat);
           handle(nullboat);
           list.RemoveAt(list.Count - 1);

           // 一个野人,一个传教士
           nullboat.setLeft_c(boat.getLeft_c() - 1 * boat.getBoat_location());
           nullboat.setLeft_y(boat.getLeft_y() - 1 * boat.getBoat_location());
           nullboat.setRight_c(boat.getRight_c() + 1 * boat.getBoat_location());
           nullboat.setRight_y(boat.getRight_y() + 1 * boat.getBoat_location());
           nullboat.setBoat_location(-boat.getBoat_location());
           list.Add(nullboat);
           handle(nullboat);
           list.RemoveAt(list.Count - 1);

           // 一个传教士过河
           nullboat.setLeft_c(boat.getLeft_c() - 1 * boat.getBoat_location());
           nullboat.setLeft_y(boat.getLeft_y());
           nullboat.setRight_c(boat.getRight_c() + 1 * boat.getBoat_location());
           nullboat.setRight_y(boat.getRight_y());
           nullboat.setBoat_location(-boat.getBoat_location());
           list.Add(nullboat);
           handle(nullboat);
           list.RemoveAt(list.Count - 1);

           // 一个野人过河       
           nullboat.setLeft_c(boat.getLeft_c());
           nullboat.setLeft_y(boat.getLeft_y() - 1 * boat.getBoat_location());
           nullboat.setRight_c(boat.getRight_c());
           nullboat.setRight_y(boat.getRight_y() + 1 * boat.getBoat_location());
           nullboat.setBoat_location(-boat.getBoat_location());
           list.Add(nullboat);
           handle(nullboat);
           list.RemoveAt(list.Count - 1);
           return 0;
        }

        private void button1_Click_1(object sender, EventArgs e)
        {         
            Left_c.Add(button12);
            Left_c.Add(button13);
            Left_c.Add(button14);
            Left_y.Add(button9);
            Left_y.Add(button10);
            Left_y.Add(button11);

            Right_c.Add(button12);
            Right_c.Add(button13);
            Right_c.Add(button14);
            Right_y.Add(button9);
            Right_y.Add(button10);
            Right_y.Add(button11);

            RiverSide start = new RiverSide(3, 3, 0, 0, 1);
            list.Add(start);
            handle(start);
            textBox1.AppendText("\n已为您找到所有过河路径,并全部已加载完毕! ^_^");
        }
    }
}


\\状态类:
public class RiverSide
{
    private int left_c;
    private int left_y;
    private int right_c;
    private int right_y;
    private int boat_location;

    public RiverSide()
    {
        // TODO 自动生成的构造函数存根
    }
    public int getLeft_c()
    {
        return left_c;
    }
    public void setLeft_c(int left_c)
    {
        this.left_c = left_c;
    }
    public int getLeft_y()
    {
        return left_y;
    }
    public void setLeft_y(int left_y)
    {
        this.left_y = left_y;
    }
    public int getRight_c()
    {
        return right_c;
    }
    public void setRight_c(int right_c)
    {
        this.right_c = right_c;
    }
    public int getRight_y()
    {
        return right_y;
    }
    public void setRight_y(int right_y)
    {
        this.right_y = right_y;
    }
    public int getBoat_location()
    {
        return boat_location;
    }
    public void setBoat_location(int boat_location)
    {
        this.boat_location = boat_location;
    }
    public RiverSide(int left_c, int left_y, int right_c, int right_y, int boat_location)
    {
        this.left_c = left_c;
        this.left_y = left_y;
        this.right_c = right_c;
        this.right_y = right_y;
        this.boat_location = boat_location;
    }
}

程序缺陷:
(1)只适用于三个传教士和三个野人渡河的情况。
(2)部分代码可能较为繁琐,重用性较差。

程序下载地址(解压即可运行)(平台:vs):https://download.csdn.net/download/qq_36260974/10790850

如有错误,欢迎指正! _


后续补充说明,后来我发现不能使用可见不可见来显示状态,是因为我没有刷新操作按钮,使用可见和不可见的方法较上面的方法容易,也易于理解,特此说明![2019 年 3 月 27 日 第一次补充]

展开阅读全文

没有更多推荐了,返回首页