C#求解独立钻石

背景

前些时日研究出华容道的计算机求解,后来得知华容道、独立钻石、魔法并称三大不可思议的智力游戏,于是下手玩起了独立钻石,没想到拿不到“天才”的称号,于是又只能跟当初解决不了华容道问题一样,动手写程序求解了。

写完后发现自己还是菜了一些,需要7分钟才能找到“天才”解法。不过已经可以满足我的虚荣心了,优化的工作就留给有缘看到这篇博文的朋友吧。

结果

求解开始
第1步==4,6,LEFT
第2步==4,3,RIGHT
第3步==4,1,RIGHT
第4步==6,4,UP
第5步==4,4,LEFT
第6步==5,6,LEFT
第7步==6,3,UP
第8步==5,1,RIGHT
第9步==5,4,LEFT
第10步==7,5,UP
第11步==7,3,RIGHT
第12步==4,5,DOWN
第13步==7,5,UP
第14步==2,5,DOWN
第15步==3,7,LEFT
第16步==3,4,RIGHT
第17步==3,2,RIGHT
第18步==5,2,UP
第19步==3,1,RIGHT
第20步==3,4,LEFT
第21步==5,7,UP
第22步==3,7,LEFT
第23步==1,3,DOWN
第24步==1,5,LEFT
第25步==4,3,UP
第26步==1,3,DOWN
第27步==3,2,RIGHT
第28步==3,4,RIGHT
第29步==5,5,UP
第30步==3,6,LEFT
第31步==2,4,DOWN
求解结束
耗时:416.4852297秒
解空间:3765429

如果朋友只是来找答案的,那么看到这里就可以了,因为接下来的内容,可能会稍微有点吃力。

源码

首先使用vs2019【可以大于等于2019版本】新建C#控制台项目,取名“独立钻石”,接着将以下【两个】cs文件拷贝到项目中即可运行。

注意事项:【编译器:VS>=2019,运行环境:C#.NET5,项目类型:控制台应用程序,电脑内存:>=16G】

请特别注意对电脑内存的要求,因为我怕你的电脑会卡死,啊哈哈。

1.Aspect.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 独立钻石
{
    public class Aspect
    {
        public bool[,] map;
        public Aspect parent = null;
        public int th = 0;
        public Tuple step = null;
        public bool success = false;

        public Aspect()
        {
            map = new bool[9, 9];
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    map[i, j] = true;
                    map[j, i] = true;
                }
            }
            map[4, 4] = false;
        }

        public Aspect(Aspect parent, Tuple step)
        {
            this.map = (bool[,])parent.map.Clone();
            this.parent = parent;
            this.step = step;
            this.th = parent.th + 1;

            int i = step.Item1, j = step.Item2;

            this.map[i, j] = !this.map[i, j];
            switch (step.Item3)
            {
                case Turn.LEFT:
                    this.map[i, j - 1] = !this.map[i, j - 1];
                    this.map[i, j - 2] = !this.map[i, j - 2];
                    break;
                case Turn.RIGHT:
                    this.map[i, j + 1] = !this.map[i, j + 1];
                    this.map[i, j + 2] = !this.map[i, j + 2];
                    break;
                case Turn.UP:
                    this.map[i - 1, j] = !this.map[i - 1, j];
                    this.map[i - 2, j] = !this.map[i - 2, j];
                    break;
                case Turn.DOWN:
                    this.map[i + 1, j] = !this.map[i + 1, j];
                    this.map[i + 2, j] = !this.map[i + 2, j];
                    break;
            }

            this.success = th == 31 && map[4, 4];
        }

        public List> Steps()
        {
            List> tuples = new();
            #region LEFT
            if (map[1, 3] == false && map[1, 4] && map[1, 5])
            {
                tuples.Add(new(1, 5, Turn.LEFT));
            }

            if (map[2, 3] == false && map[2, 4] && map[2, 5])
            {
                tuples.Add(new(2, 5, Turn.LEFT));
            }

            if (map[6, 3] == false && map[6, 4] && map[6, 5])
            {
                tuples.Add(new(6, 5, Turn.LEFT));
            }

            if (map[7, 3] == false && map[7, 4] && map[7, 5])
            {
                tuples.Add(new(7, 5, Turn.LEFT));
            }

            for (int i = 3; i <= 5; i++)
            {
                for (int j = 1; j <= 5; j++)
                {
                    if (map[i, j] == false && map[i, j + 1] && map[i, j + 2])
                    {
                        tuples.Add(new(i, j + 2, Turn.LEFT));
                    }
                }
            }
            #endregion

            #region RIGHT
            if (map[1, 5] == false && map[1, 4] && map[1, 3])
            {
                tuples.Add(new(1, 3, Turn.RIGHT));
            }

            if (map[2, 5] == false && map[2, 4] && map[2, 3])
            {
                tuples.Add(new(2, 3, Turn.RIGHT));
            }

            if (map[6, 5] == false && map[6, 4] && map[6, 3])
            {
                tuples.Add(new(6, 3, Turn.RIGHT));
            }

            if (map[7, 5] == false && map[7, 4] && map[7, 3])
            {
                tuples.Add(new(7, 3, Turn.RIGHT));
            }

            for (int i = 3; i <= 5; i++)
            {
                for (int j = 7; j >= 3; j--)
                {
                    if (map[i, j] == false && map[i, j - 1] && map[i, j - 2])
                    {
                        tuples.Add(new(i, j - 2, Turn.RIGHT));
                    }
                }
            }
            #endregion

            #region UP
            if (map[3, 1] == false && map[4, 1] && map[5, 1])
            {
                tuples.Add(new(5, 1, Turn.UP));
            }

            if (map[3, 2] == false && map[4, 2] && map[5, 2])
            {
                tuples.Add(new(5, 2, Turn.UP));
            }

            if (map[3, 6] == false && map[4, 6] && map[5, 6])
            {
                tuples.Add(new(5, 6, Turn.UP));
            }

            if (map[3, 7] == false && map[4, 7] && map[5, 7])
            {
                tuples.Add(new(5, 7, Turn.UP));
            }

            for (int i = 1; i <= 5; i++)
            {
                for (int j = 3; j <= 5; j++)
                {
                    if (map[i, j] == false && map[i + 1, j] && map[i + 2, j])
                    {
                        tuples.Add(new(i + 2, j, Turn.UP));
                    }
                }
            }
            #endregion

            #region DOWN
            if (map[5, 1] == false && map[4, 1] && map[3, 1])
            {
                tuples.Add(new(3, 1, Turn.DOWN));
            }

            if (map[5, 2] == false && map[4, 2] && map[3, 2])
            {
                tuples.Add(new(3, 2, Turn.DOWN));
            }

            if (map[5, 6] == false && map[4, 6] && map[3, 6])
            {
                tuples.Add(new(3, 6, Turn.DOWN));
            }

            if (map[5, 7] == false && map[4, 7] && map[3, 7])
            {
                tuples.Add(new(3, 7, Turn.DOWN));
            }

            for (int i = 7; i >= 3; i--)
            {
                for (int j = 3; j <= 5; j++)
                {
                    if (map[i, j] == false && map[i - 1, j] && map[i - 2, j])
                    {
                        tuples.Add(new(i - 2, j, Turn.DOWN));
                    }
                }
            }
            #endregion

            return tuples;
        }
    }

    public enum Turn
    {
        LEFT,
        RIGHT,
        UP,
        DOWN
    }
}

2.Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 独立钻石
{
    class Program
    {
        static void Main()
        {
            DateTime start = DateTime.Now;
            int count = 0;
            Console.WriteLine("求解开始");

            Aspect aspect = new();

            Queue queue = new();
            queue.Enqueue(aspect);
            HashSet set = new();
            while (queue.Any())
            {
                Aspect now = queue.Dequeue();
                List> steps = now.Steps();
                foreach (var step in steps)
                {
                    Aspect next = new(now, step);

                    string self = ToString(next.map);
                    if (!set.Contains(self))
                    {
                        List mays = 各种变换(next.map);
                        foreach (string may in mays)
                        {
                            try
                            {
                                set.Add(may);
                            }
                            catch { }
                        }

                        queue.Enqueue(next);
                        //stack.Push(next);
                        count = Math.Max(queue.Count, count);
                        if (next.success)
                        {
                            Print(next);
                            break;
                        }
                    }
                }
            }

            Console.WriteLine("求解结束");
            DateTime end = DateTime.Now;
            Console.WriteLine($"耗时:{(end - start).TotalSeconds}秒");
            Console.WriteLine($"解空间:{count}");
        }

        public static void Print(Aspect aspect)
        {
            if (aspect.parent != null)
            {
                Print(aspect.parent);
            }

            if (aspect.step != null)
            {
                Console.WriteLine($"第{aspect.th}步=={aspect.step.Item1},{aspect.step.Item2},{aspect.step.Item3}==>{ToString(aspect.map)}");
            }
        }

        public static string ToString(bool[,] map)
        {
            StringBuilder sb = new();
            for (int i = 1; i <= 7; i++)
            {
                int line = 0;
                for (int j = 1; j <= 7; j++)
                {
                    if (map[i, j])
                    {
                        line |= (1 << (j - 1));
                    }
                }
                sb.Append((char)line);
            }
            return sb.ToString();
        }

        public static List 各种变换(bool[,] map)
        {
            List result = new();
            //垂直轴对称
            //水平轴对称
            //撇轴对称
            //捺轴对称
            //顺时针90度
            //逆时针90度
            //中心对称

            StringBuilder[] sbs = new StringBuilder[8];
            for(int i = 0; i < sbs.Length; i++)
            {
                sbs[i] = new();
            }
            int[] line = new int[8];

            for (int i = 1; i <= 7; i++)
            {
                for(int j = 0; j < 8; j++)
                {
                    line[j] = 0;
                }
                for (int j = 1; j <= 7; j++)
                {
                    if(map[i, 8 - j])
                    {
                        line[0] |= (1 << (j - 1));
                    }
                    if(map[8 - i, j])
                    {
                        line[1] |= (1 << (j - 1));
                    }
                    if(map[j, i])
                    {
                        line[2] |= (1 << (j - 1));
                    }
                    if(map[8 - j, 8 - i])
                    {
                        line[3] |= (1 << (j - 1));
                    }
                    if(map[8 - i, 8 - j])
                    {
                        line[4] |= (1 << (j - 1));
                    }
                    if (map[j, 8 - i])
                    {
                        line[5] |= (1 << (j - 1));
                    }
                    if(map[8 - j, i])
                    {
                        line[6] |= (1 << (j - 1));
                    }
                    if (map[i, j])
                    {
                        line[7] |= (1 << (j - 1));
                    }
                }

                for(int j = 0; j < 8; j++)
                {
                    sbs[j].Append((char)line[j]);
                }
            }

            for(int i = 0; i < 8; i++)
            {
                result.Add(sbs[i].ToString());
            }

            return result;
        }
    }
}

解析

数据结构

把棋盘假想成9X9的二维平面,为啥不是7X7,因为周边扩充后,我们方便边沿判断,具体方案如下:

1.把棋盘设置为9X9的布尔矩阵map[9,9]

2.除了中间的点map[4,4]为false,其余均为true

3.那么我们在计算当前局面的可行步时,便可以大大方便边沿判断

算法

广度优先,毋庸置疑。

但是,难点在于剪枝,去重。

注意到每个局面最终都是7X7的,而且棋子只有两种可能,有和无,非常适合使用0和1来代替

我们看初始局面,就像是

0 0 1 1 1 0 0
0 0 1 1 1 0 0
1 1 1 1 1 1 1
1 1 1 0 1 1 1
1 1 1 1 1 1 1
0 0 1 1 1 0 0
0 0 1 1 1 0 0 

 把每一行看作一个二进制的数字,可以转换成

第一行 30
第二行 30
第三行 127
第四行 119
第五行 127
第六行 30
第七行 30

由于每个数字即使最大也才127,因此我们可以把它们转换成ASCII字符 ,并串起来形成长度仅7个字符的字符串,这样就可以轻松表示一个局面,而且可以确保不可能重复,这个不需要证明吧。

C#求解独立钻石_第1张图片

解决了存储局面的问题,还要剪枝,我们知道,一个正方形,你可以对它进行7种变换都属于类似的自己,分别是【水平竖直轴对称,撇捺轴对称,中心对称=旋转180度,顺逆时针旋转90度】,再加上本身的话,就是每次移动最多可以确定8个局面算是重复的,注意最多,因为有可能其进行一些变换后与原来相同。

把以上两个问题想通透之后,具体代码相信大家就可以自己写出来了。

当然,如果不嫌在下代码的性能问题,也可以稍作参考。

你可能感兴趣的:(C#,算法,c#,算法,数据结构,广度优先)