【独立钻石C#解法】

独立钻石C#解法,可以得到2个18步最优解


偶然看到独立钻石游戏,想用计算机求解一下。
这个问题网上深度优先算法较多,空间复杂度低,但是优化比较难做,时间复杂度高,觉得广度优先更合适。于是找到 氷凌公子 的一个广度优先算法,拿来调试优化了一下。能找到解了,但是只找到一个解就完事大吉了,现在看来是剪枝的时候直接把重复的盘面都剪掉了。相同的盘面有不同的步骤,要找到所有的最优解就不能这样剪枝。相同盘面时要保留连步数最小的多个盘面。这样空间复杂度又要增大了。
代码贴在这里,大家无聊时可以参考解闷。

找不到最优解是因为按照盘面做的重复检查,相同盘面保留一个,而得到此盘面的路径不同,直接剪枝就可能剪掉了最优解。
基于此,对盘面和最后落子点合并做盘面hash,然后再剪枝,就能保留最优解了。但是也只是保证保留了一个最优解,剪掉的仍然可以是另一个最优解。
求所有最优解,就要盘面相同和步骤相同时保留盘面继续搜索不剪枝掉。但是这样空间复杂度剧增,时间复杂度也同步增加。现在先考虑保证输出一组最优解。

现在我64G内存的笔记本,程序运行能得到一组最优解。
最耗时的是检查哈希表中有没有重复盘面并获取这个盘面的最小步长。这个操作耗时60%以上。思前想后的也没想出什么替代方案。现在用的是dictionary。

优化方面做的内容有2维数组改1维数组,bool的棋盘改byte,去掉了盘面的parent,早点释放旧盘面方便GC。用int64替换了string的hash值类型,增加了棋步列表(去掉parent的代价,以前棋步路径从parent追溯得到),并且直接用变量没有用数组,初始化一个数组竟然发现挺浪费时间的。优化的对称算法。所有可走的棋步用静态数组保存了。

剪枝的原理就是遇到落点相同盘面相同的时候,保留步数较少的。大于等于的剪枝处理。关键是落点相同这个条件保证会留下一个最优解。

改了一下,Print没弄好。先用parent链吧。大概2个半小时能出结果吧。

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.


Board.cs

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace 独立钻石
{
    public class Board//盘面
    {
        public byte[] map; //= new bool[7][];//bool 占用一个字节。2.82%
        //public Board parent;//这个属性会导致记录下一个盘面的链,后续打印用到。现在加一个stepLink数组记录历史棋步。
        public int th = 0;//深度
        public Step step;//棋步
        public bool success = false;//成功标识
        public int linkStepCnt = 0;//按连步模式计算的步数。
        public Int64 selfHash =0;//盘面hash值,以盘面和最后落点计算生成。盘面相同落点相同才算重复盘面。
                                 //        public int[] stepLink=new int[33];//每个盘面都有这个数组的new操作,占cpu。但是可以去掉parent,释放盘面链。
                                 //public List<Step> stepList = new();//放到外部,不每次new,但是要每次clear。挪到主程序中作为一个变量。不放在盘面中持久保存。
        //记录棋步路径。31步。不用数组。能快一点
        public int t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16,
                   t17, t18, t19, t20, t21, t22, t23, t24, t25, t26, t27, t28, t29, t30, t31;
        //public byte[] stepIdList ;//= new byte[32];
           
        public Board()//初始化盘面,棋局开始的时候用这个一次。后续用下面的那个构造函数。 
        {
            map = new byte[] 
            {
                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  
            };
            //stepIdList= new byte[32];

        }
        public static byte[] dot = new byte[] 
        {
                0 ,1 ,2 ,3 ,4 ,5 ,6 ,
                7 ,8 ,9 ,10,11,12,13,
                14,15,16,17,18,19,20,
                21,22,23,24,25,26,27,
                28,29,30,31,32,33,34,
                35,36,37,38,39,40,41,
                42,43,44,45,46,47,48 
        };
//初始化所有能走的步。不管有没有子。走棋的时候不再判断棋盘边界条件,只判断有没有棋子的条件。
public static Step[] allSteps = new Step[]
        {
            //上边6子
            new Step(0, 2, 0, 3, 0, 4, Dir.RIGHT,0,2,3,4)  ,new Step( 0,2,1,2,2,2,Dir.DOWN ,1,2,9,16),
                                                            new Step( 0,3,1,3,2,3,Dir.DOWN ,2,3,10,17),                
            new Step( 0,4,0,3,0,2,Dir.LEFT ,3,4,3,2),       new Step( 0,4,1,4,2,4,Dir.DOWN ,4,4,11,18),    
            new Step( 1,2,1,3,1,4,Dir.RIGHT ,5,9,10,11),    new Step( 1,2,2,2,3,2,Dir.DOWN ,6,9,16,23),    
                                                            new Step( 1,3,2,3,3,3,Dir.DOWN ,7,10,17,24),    
            new Step( 1,4,1,3,1,2,Dir.LEFT,8,11,10,9 ),     new Step( 1,4,2,4,3,4,Dir.DOWN ,9,11,18,25),    
            //左边6子                                                                                         
            new Step( 2,0,2,1,2,2,Dir.RIGHT ,10,14,15,16),new Step( 2,0,3,0,4,0,Dir.DOWN ,11,14,21,28),       
            new Step( 3,0,3,1,3,2,Dir.RIGHT ,12,21,22,23),                                                            
            new Step( 4,0,4,1,4,2,Dir.RIGHT ,13,28,29,30),new Step( 4,0,3,0,2,0,Dir.UP ,14,28,21,14),                          
            new Step( 2,1,2,2,2,3,Dir.RIGHT ,15,15,16,17),new Step( 2,1,3,1,4,1,Dir.DOWN ,16,15,22,29),                      
            new Step( 3,1,3,2,3,3,Dir.RIGHT ,17,22,23,24),                                                            
            new Step( 4,1,4,2,4,3,Dir.RIGHT ,18,29,30,31),new Step( 4,1,3,1,2,1,Dir.UP ,19,29,22,15),       
            //中间9子
            new Step( 2,2,2,1,2,0,Dir.LEFT ,20,16,15,14),new Step( 2,2,2,3,2,4,Dir.RIGHT ,21,16,17,18),new Step( 2,2,1,2,0,2,Dir.UP ,22,16,9,2), new Step( 2,2,3,2,4,2,Dir.DOWN,23,16,23,30 ),
            new Step( 2,3,2,2,2,1,Dir.LEFT ,24,17,16,15),new Step( 2,3,2,4,2,5,Dir.RIGHT ,25,17,18,19),new Step( 2,3,1,3,0,3,Dir.UP ,26,17,10,3),new Step( 2,3,3,3,4,3,Dir.DOWN,27,17,24,31 ),
            new Step( 2,4,2,3,2,2,Dir.LEFT ,28,18,17,16),new Step( 2,4,2,5,2,6,Dir.RIGHT ,29,18,19,20),new Step( 2,4,1,4,0,4,Dir.UP ,30,18,11,4),new Step( 2,4,3,4,4,4,Dir.DOWN,31,18,25,32 ),
            new Step( 3,2,3,1,3,0,Dir.LEFT ,32,23,22,21),new Step( 3,2,3,3,3,4,Dir.RIGHT ,33,23,24,25),new Step( 3,2,2,2,1,2,Dir.UP ,34,23,16,9),new Step( 3,2,4,2,5,2,Dir.DOWN,35,23,30,37 ),
            new Step( 3,3,3,2,3,1,Dir.LEFT ,36,24,23,22),new Step( 3,3,3,4,3,5,Dir.RIGHT ,37,24,25,26),new Step( 3,3,2,3,1,3,Dir.UP ,38,24,17,10),new Step( 3,3,4,3,5,3,Dir.DOWN,39,24,31,38 ),
            new Step( 3,4,3,3,3,2,Dir.LEFT ,40,25,24,23),new Step( 3,4,3,5,3,6,Dir.RIGHT ,41,25,26,27),new Step( 3,4,2,4,1,4,Dir.UP ,42,25,18,11),new Step( 3,4,4,4,5,4,Dir.DOWN,43,25,32,39 ),
            new Step( 4,2,4,1,4,0,Dir.LEFT ,44,30,29,28),new Step( 4,2,4,3,4,4,Dir.RIGHT ,45,30,31,32),new Step( 4,2,3,2,2,2,Dir.UP ,46,30,23,16),new Step( 4,2,5,2,6,2,Dir.DOWN,47,30,37,44 ),
            new Step( 4,3,4,2,4,1,Dir.LEFT ,48,31,30,29),new Step( 4,3,4,4,4,5,Dir.RIGHT ,49,31,32,33),new Step( 4,3,3,3,2,3,Dir.UP ,50,31,24,17),new Step( 4,3,5,3,6,3,Dir.DOWN,51,31,38,45 ),
            new Step( 4,4,4,3,4,2,Dir.LEFT ,52,32,31,30),new Step( 4,4,4,5,4,6,Dir.RIGHT ,53,32,33,34),new Step( 4,4,3,4,2,4,Dir.UP ,54,32,25,18),new Step( 4,4,5,4,6,4,Dir.DOWN,55,32,39,46 ),
            //下边6子
            new Step( 5,2,5,3,5,4,Dir.RIGHT ,56,37,38,39),new Step( 5,2,4,2,3,2,Dir.UP,57,37,30,23 ),   
                                                          new Step( 5,3,4,3,3,3,Dir.UP,58,38,31,24 ),                   
            new Step( 5,4,5,3,5,2,Dir.LEFT ,59,39,38,37), new Step( 5,4,4,4,3,4,Dir.UP,60,39,32,25 ),                   
            new Step( 6,2,6,3,6,4,Dir.RIGHT,61,44,45,46), new Step( 6,2,5,2,4,2,Dir.UP,62,44,37,30 ),                
                                                          new Step( 6,3,5,3,4,3,Dir.UP,63,45,38,31 ),    
            new Step( 6,4,6,3,6,2,Dir.LEFT ,64,46,45,44), new Step( 6,4,5,4,4,4,Dir.UP,65,46,39,32 ),   
            //右边6子                                                                                   
            new Step( 2,5,2,4,2,3,Dir.LEFT ,66,19,18,17),new Step( 2,5,3,5,4,5,Dir.DOWN,67,19,26,33 ),
            new Step( 3,5,3,4,3,3,Dir.LEFT ,68,26,25,24),
            new Step( 4,5,4,4,4,3,Dir.LEFT ,69,33,32,31),new Step( 4,5,3,5,2,5,Dir.UP ,70,33,26,19),   
            new Step( 2,6,2,5,2,4,Dir.LEFT ,71,20,19,18),new Step( 2,6,3,6,4,6,Dir.DOWN ,72,20,27,34), 
            new Step( 3,6,3,5,3,4,Dir.LEFT ,73,27,26,25),                                     
            new Step( 4,6,4,5,4,4,Dir.LEFT ,74,34,33,32),new Step( 4,6,3,6,2,6,Dir.UP ,75,34,27,20)    
        };                                                                           
                                                                                     
        public Board(Board parentBd,  Step step)//根据上一个盘面和当前棋步生成新的盘面。 
        {
            //this.map = (Byte[])parentBoard.map.Clone();//2维数组直接clone即可。Clone隐含了new的过程。copy需要提前new。
            map = new byte[49];                 //clone 最慢,直接带值new次之,new然后copy最快,copy快,消耗在new上。
            Array.Copy(parentBd.map, map, 49);

            //复制上一步的棋步路径。
            t0  = parentBd.t0;  t1  = parentBd.t1;  t2  = parentBd.t2;  t3  = parentBd.t3; t4  =  parentBd.t4;  t5  = parentBd.t5;  t6  = parentBd.t6;  t7  = parentBd.t7;  t8  = parentBd.t8;  t9 = parentBd.t9;
            t10 = parentBd.t10; t11 = parentBd.t11; t12 = parentBd.t12; t13 = parentBd.t13;t14 =  parentBd.t14; t15 = parentBd.t15; t16 = parentBd.t16; t17 = parentBd.t17; t18 = parentBd.t18; t19 =parentBd.t19;
            t20 = parentBd.t20; t21 = parentBd.t21; t22 = parentBd.t22; t23 = parentBd.t23;t24 =  parentBd.t24; t25 = parentBd.t25; t26 = parentBd.t26; t27 = parentBd.t27; t28 = parentBd.t28; t29 =parentBd.t29;
            t30 = parentBd.t30; t31 = parentBd.t31;

            //当前棋步是第几步,就把当前棋步的id保存在第几步的变量中。
            switch (parentBd.th+1)
            {
                case 0: t0 = step.id; break;case 10: t10 = step.id; break;case 20: t20 = step.id; break;case 30: t30 = step.id; break;
                case 1: t1 = step.id; break;case 11: t11 = step.id; break;case 21: t21 = step.id; break;case 31: t31 = step.id; break;
                case 2: t2 = step.id; break;case 12: t12 = step.id; break;case 22: t22 = step.id; break;
                case 3: t3 = step.id; break;case 13: t13 = step.id; break;case 23: t23 = step.id; break;
                case 4: t4 = step.id; break;case 14: t14 = step.id; break;case 24: t24 = step.id; break;
                case 5: t5 = step.id; break;case 15: t15 = step.id; break;case 25: t25 = step.id; break;
                case 6: t6 = step.id; break;case 16: t16 = step.id; break;case 26: t26 = step.id; break;
                case 7: t7 = step.id; break;case 17: t17 = step.id; break;case 27: t27 = step.id; break;
                case 8: t8 = step.id; break;case 18: t18 = step.id; break;case 28: t28 = step.id; break;
                case 9: t9 = step.id; break;case 19: t19 = step.id; break;case 29: t29 = step.id; break;
            } 
            this.parent = parent; //目前打印路径的时候要顺着链表回溯得到路径。取消parent可以降低空间复杂度,但是要记录路径链表,性能不好。。
            this.step = step;//
            this.th = parentBd.th + 1;

            this.map[step.from] = 0;// 起跳位提子
            this.map[step.mid] = 0;// 中间位提子
            this.map[step.to] = 1; // 到达位落子

            this.success = (th == 31 && map[24]==1);//31深度说明只剩一个棋子,map[3,3]表示棋子在棋盘中央,表示成功的一种走法。
            this.linkStepCnt = parentBd.linkStepCnt;
            //不是连步,累加步数,连步不累加。
            if (parentBd.step.to !=  this.step.from) // 前落点!=现起点
                {
                    this.linkStepCnt++;
            }
        }
        /// <summary>
        /// 棋步结构体,记录一步棋的3个点。使用时不用按方向现计算。
        /// 棋盘也可以表示为一维数组,这时棋步要记录一维数组的值作为位置标识。可以考虑优化。一维数组性能更好,但是可能带来别的复杂性,需要权衡比较,也许在这里能有帮助。
        /// </summary>
        public struct Step
        {
            public byte x1, y1, x2, y2, x3, y3,from,mid,to;
            public Dir dir;//走棋方向,方便显示。计算时不用,打印时方便点。
            public int id;
            public Step(byte p1, byte q1, byte p2, byte q2, byte p3, byte q3, Dir dr,
                int stepId,byte fromDot,byte midDot,byte toDot)
            {
                x1 = q1; y1 = p1;
                x2 = q2; y2 = p2;
                x3 = q3; y3 = p3;
                dir = dr;id = stepId;
                from = fromDot;mid = midDot;to = toDot;
            }
        }

        /// <summary>
        /// 获取当前盘面可以走的所有有效棋步。
        /// </summary>
        /// <returns></returns>
        //public List<Step> GetSteps()//生成当前盘面所有有效走法列表
        public void GetSteps(List<Step> stepList)//生成当前盘面所有有效走法列表
        {
            stepList.Clear();
            foreach (Step step in allSteps)
            {
                //if (map[step.y3][ step.x3] == 0 &&  //落子位无子
                //    map[step.y2][ step.x2] == 1 &&           //跳过位有子
                //    map[step.y1][ step.x1] == 1              //起跳位有子
                if (map[step.to] == 0 &&  //落子位无子
                    map[step.mid] == 1 &&           //跳过位有子
                    map[step.from] == 1              //起跳位有子
                    )
                { stepList.Add(step); }
            }
            //return stepList;
            return;
        }
    }

    public enum Dir //行棋方向
    {
        LEFT,
        RIGHT,
        UP,
        DOWN
    }
}

program.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection.Metadata;
using System.Text;
using static System.Collections.Specialized.BitVector32;
using static System.Net.Mime.MediaTypeNames;
using static 独立钻石.Board;
https://blog.csdn.net/qq_36694133/article/details/124332614
//本算法是从网上找来的,采用广度优先,根据盘面对称剪枝。
//深度优先内存占用少,但是没有有效的剪枝手段。
//V1版本做性能优化。
//1,交错数组改 2维数组版本。发现性能差不多。
//2,hash标识在盘面dequeue时同步删除。降低空间复杂度。
//3,加上连步计算。
//4,加输出
//5,深度小于31才入队列。31深度时只出不入,处理完循环结束。
//6,v3版需要处理只搜索到一个结果就结束的问题。原因找到了,是相同盘面到达的连步数不同,不能直接剪枝掉。
//

//这个版本修改了剪枝策略。一个盘面发现是重复的,但是连步数大于之前盘面的连步数,则抛弃。否则盘面入队列。
//一个盘面只入一次hash表。对称的不入hash表。节省点空间。
//这个版本耗时900秒以内,能得到两个解。还是不对。

//改交错数组。
//盘面hash字符串改int64.

//剪枝算法如果把重复和对称盘面都剪掉,那么就会剪掉很多走法,所以得不到所有最优解。目前没想到高效的剪枝方法。
//只有剪掉某一个盘面步数较大的分支。保留最小的,但是最小的会是多个。包括对称的。
//先尝试获得一个最优解吧。
//这个版本目前获得了20步的解。

//这个版本根据map和落点做hash。落点相同盘面相同才算重复,都做对称变换来判断。
//但是仍然有盘面相同但是路径不同步数相等的情况,仍然会被剪枝掉。如果再做一次按路径的hash判断能保证精准。暂时不做了。
对称hash值优化掉判断和循环。
///map改byte。byte计算hash值时直接计算不用类型转换。
//去掉parent,增加linkstep,记录棋步链。增加了cpu消耗。去掉linkstep数组,直接用属性值。丑了点。快了点。
//map改1维数组。
//最终能得到两个18步的解了。理论上还是有点瑕疵。


namespace 独立钻石
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("求解开始");
            DateTime start = DateTime.Now;//开始时间。
            Board board = new();//初始盘面。
            Queue<Board> queue = new();//盘面队列。
            queue.Enqueue(board);//第一个盘面入队列。深度0,开始只有这一个盘面。
            Dictionary<Int64, int> set = new();//保存盘面的hash标识和盘面的连步数量。用来判断是否是重复盘面和连步是否最小。重复的进行剪枝。连步不是最小的剪枝。队列不便搜索和修改步数等操作。
            set.Add(board.selfHash, board.linkStepCnt);//保存hash值和连步数量。
            int deep = 0;
            int lastDeep = 0;
            int minLinkSteps = 31;
            bool exist ;
            int lastMinStep;
            long boardCount = 0;
            Int64 boardHash;
            List<Step> stepList = new();//放到外部,不每次new,但是要每次clear。挪到主程序中。不放在盘面中。
            while (queue.Any())
            {
                Board now = queue.Dequeue();//上一个深度的一个盘面出队列。注意,出队列必定是上一个深度的。
                deep = now.th + 1;
                if (deep > lastDeep)//进入下一个深度了。打印一下求解进度情况
                {
                    lastDeep = deep;
                    DateTime mid = DateTime.Now;
                    Console.WriteLine($"耗时:{(mid - start).TotalSeconds}秒");
                    Console.WriteLine($"搜索深度:{deep},hashSet长度:{set.Count()},队列长度:{queue.Count()}");
                    Console.WriteLine($"盘面总数:{boardCount},入hashSet数量:{hashSetCount},入队列盘面数量:{equeueCount}");
                }

                boardHash = now.selfHash;//盘面自身hash值计算出来后保存在盘面中,不用再计算,直接取。
                exist = set.TryGetValue(boardHash, out lastMinStep);//1.02%检查盘面是否在字典表中,以及获取盘面最小步数。
                if (exist)//盘面还有效。
                {
                    if (now.linkStepCnt > lastMinStep)//队列棋步已经不是最小步了就不再继续走了。这个最小步数是别的棋步改的。
                    {
                        continue;
                    }
                    set.Remove(boardHash);// 出队列一个盘面,就相应删除hash表中的盘面hash标识。
                }
                else //不存在是因为其他路径遇到重复盘面,且步数更少,于是删掉了这个hash值。
                    continue;   //现在这个程序不求所有最优解了,已处理过的盘面直接剪枝。

                //List<Step> steps = 
               // now.GetSteps(stepList);//4.35% 生成盘面能走的所有棋步
                // List<Step> stepList = new();//放到外部,不每次new,但是要每次clear。挪到主程序中。不放在盘面中。
                /
                ///
                foreach (Step step in allSteps)//直接循环生成棋步并处理,不再用方法生成一个棋步列表再重新循环处理。
                {
                    if (!(now.map[step.to] == 0 &&  //落子位无子
                        now.map[step.mid] == 1 &&           //跳过位有子
                        now.map[step.from] == 1              //起跳位有子
                        ))
                    { continue; }
                //}

                //foreach (Step step in stepList)//循环处理所有棋步。
                //{
                    //25.14% 第二耗时的语句,给新盘面分配内存导致的,包括棋步链和棋盘。也许设计一个算法不用数组就好了。
                    Board next = new Board(now, step); //根据当前棋步生成新的盘面。这是下一个深度的盘面。
                    boardCount++;

                    if (next.success)//成功的盘面,打印信息。
                    {
                        resultNum++;
                        if (next.linkStepCnt <= minLinkSteps)//这个是一边生成一边打印,会从头开始打印,不都是最优解,遇到小的就不再打印大的了。
                        {
                            minLinkSteps = Math.Min(minLinkSteps, next.linkStepCnt);//记录最小步数。大于最小步数的不再打印。
                            Print(next);
                        }
                        continue;//先判断是否success,然后continue,不做后续的剪枝操作,避免最后一步把落点相同的步骤剪枝了。
                    }

                    bool valid = true;//用来标识这个棋步是否有效。无效的直接剪枝。
                    for (int i = 0; i < 8; i++)//循环8种对称方式,包括自身。注意:自身为0,第一个生成。
                    {
                        Sym sym = (Sym)i;
                        boardHash = GetSymmetryHash(next.map, now.step.y3, now.step.x3, sym);//11.96%生成新盘面的8种对称方式的hash值。
                        if (sym == Sym.SELF)
                        {
                            next.selfHash = boardHash;//记录自身hash值。
                        }
                        //    
                        exist = set.TryGetValue(boardHash, out lastMinStep);//45.17% 最耗时的语句。判断盘面存在性和获取之前的最小步数。
                        if (!exist)
                        {
                            valid = true;  //不存在。可能是新盘面,继续判断其他对称盘面。
                            continue;
                        }
                        if (next.linkStepCnt > lastMinStep) //重复盘面,新盘面连步数大于已有盘面,定为无效盘面,剪枝掉。
                        {
                            valid = false;
                            break;
                        }
                        else if (next.linkStepCnt < lastMinStep)  //重复盘面,新盘面连步步数小于已有盘面,旧盘面就无效了,删除hash值。在出队列时会发现hash值没有了,就进行剪枝。
                        {
                            valid = true;//新盘面有效。
                            set.Remove(boardHash);//旧盘面删除hash值,相当于预剪枝。queue无法操作,等出队列时处理。
                            break;
                        }
                        else //next.linkStepCnt == lastMinStep 重复盘面,步数相等。保留旧的。新盘面剪枝处理。
                        {
                            valid = false; //这样剪枝可能剪掉其他最优解。因为重复盘面可能得到的路径不同,虽然步数相同,可能属于不同的最优解。
                            //难点就在这里!!!相同盘面步数相同,如果保留,只能入队列,hash值保留前一个的。出队列时不能根据无hash来剪枝,空间复杂度增幅太大。
                            break;
                        }
                    }

                    if (!valid)
                    {
                        continue;//无效的盘面剪枝掉。
                    }

                    set.Add(next.selfHash, next.linkStepCnt);//有效盘面保存hash值。注意,这里保存的是盘面自身hash值,
                    hashSetCount++;
                    if (next.th < 31) //最后一步不再入队列了,搜索完队列所有30深度数据就结束了。
                    {
                        queue.Enqueue(next);//有效盘面入队列。
                        equeueCount++;
                    }
                    
                }

            }
            Console.WriteLine("求解结束");
            DateTime end = DateTime.Now;
            Console.WriteLine($"耗时:{(end - start).TotalSeconds}秒");
            Console.WriteLine($"解空间:{resultNum},最小连步:{minLinkSteps}");
            Console.WriteLine($"搜索深度:{deep},hashSet长度:{set.Count()},队列长度:{queue.Count()}");
            Console.WriteLine($"盘面总数:{boardCount},入hashSet数量:{hashSetCount},入队列盘面数量:{equeueCount}");
        }

        static int equeueCount = 0;
        static int hashSetCount = 0;
        static int resultNum = 0;
        public static void Print(Board bd)
        {
            //盘面链方式显示棋步路径
            //if (bd.parent != null)
            //{
            //    Print(bd.parent);//盘面有parent时,递归显示棋步。
            //}
            //Step step = bd.step;
            //Console.WriteLine($"第{bd.th}步:{bd.step.y1},{bd.step.x1}==>{bd.step.y2},{bd.step.x2}==>{bd.step.y3},{bd.step.x3},方向:{bd.step.dir},连步数:{bd.linkStepCnt}");

            //棋步列表方式显示棋步路径。每一步的连步没有了。需要补充显示每一步连步数的逻辑。
            //Step step;
            //for(int i = 1;i < 32;i++)
            //{
            //    step = allSteps[bd.stepIdList[i]];
            //    Console.WriteLine($"第{i}步:{step.y1},{step.x1}==>{step.y2},{step.x2}==>{step.y3},{step.x3},方向:{step.dir}");
            //}
            //Console.WriteLine($"连步数:{bd.linkStepCnt}");

            //打印棋盘。
            //for (int i = 0; i < 7; i++)
            //{
            //    for (int j = 0; j < 7; j++)
            //    {
            //        Console.Write($"{bd.map[i*7+j]},");
            //    }
            //    Console.WriteLine("");
            //}
            //非数组方式显示棋步路径。同样存在连步显示问题。
                //浪一下的代价。
            Console.WriteLine("================");
            Console.WriteLine($"第 1 步:{allSteps[bd.t1].y1},{allSteps[bd.t1].x1}==>{allSteps[bd.t1].y2},{allSteps[bd.t1].x2}==>{allSteps[bd.t1].y3},{allSteps[bd.t1].x3},方向:{allSteps[bd.t1].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 2 步:{allSteps[bd.t2].y1},{allSteps[bd.t2].x1}==>{allSteps[bd.t2].y2},{allSteps[bd.t2].x2}==>{allSteps[bd.t2].y3},{allSteps[bd.t2].x3},方向:{allSteps[bd.t2].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 3 步:{allSteps[bd.t3].y1},{allSteps[bd.t3].x1}==>{allSteps[bd.t3].y2},{allSteps[bd.t3].x2}==>{allSteps[bd.t3].y3},{allSteps[bd.t3].x3},方向:{allSteps[bd.t3].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 4 步:{allSteps[bd.t4].y1},{allSteps[bd.t4].x1}==>{allSteps[bd.t4].y2},{allSteps[bd.t4].x2}==>{allSteps[bd.t4].y3},{allSteps[bd.t4].x3},方向:{allSteps[bd.t4].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 5 步:{allSteps[bd.t5].y1},{allSteps[bd.t5].x1}==>{allSteps[bd.t5].y2},{allSteps[bd.t5].x2}==>{allSteps[bd.t5].y3},{allSteps[bd.t5].x3},方向:{allSteps[bd.t5].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 6 步:{allSteps[bd.t6].y1},{allSteps[bd.t6].x1}==>{allSteps[bd.t6].y2},{allSteps[bd.t6].x2}==>{allSteps[bd.t6].y3},{allSteps[bd.t6].x3},方向:{allSteps[bd.t6].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 7 步:{allSteps[bd.t7].y1},{allSteps[bd.t7].x1}==>{allSteps[bd.t7].y2},{allSteps[bd.t7].x2}==>{allSteps[bd.t7].y3},{allSteps[bd.t7].x3},方向:{allSteps[bd.t7].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 8 步:{allSteps[bd.t8].y1},{allSteps[bd.t8].x1}==>{allSteps[bd.t8].y2},{allSteps[bd.t8].x2}==>{allSteps[bd.t8].y3},{allSteps[bd.t8].x3},方向:{allSteps[bd.t8].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 9 步:{allSteps[bd.t9].y1},{allSteps[bd.t9].x1}==>{allSteps[bd.t9].y2},{allSteps[bd.t9].x2}==>{allSteps[bd.t9].y3},{allSteps[bd.t9].x3},方向:{allSteps[bd.t9].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 10步:{allSteps[bd.t10].y1},{allSteps[bd.t10].x1}==>{allSteps[bd.t10].y2},{allSteps[bd.t10].x2}==>{allSteps[bd.t10].y3},{allSteps[bd.t10].x3},方向:{allSteps[bd.t10].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 11步:{allSteps[bd.t11].y1},{allSteps[bd.t11].x1}==>{allSteps[bd.t11].y2},{allSteps[bd.t11].x2}==>{allSteps[bd.t11].y3},{allSteps[bd.t11].x3},方向:{allSteps[bd.t11].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 12步:{allSteps[bd.t12].y1},{allSteps[bd.t12].x1}==>{allSteps[bd.t12].y2},{allSteps[bd.t12].x2}==>{allSteps[bd.t12].y3},{allSteps[bd.t12].x3},方向:{allSteps[bd.t12].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 13步:{allSteps[bd.t13].y1},{allSteps[bd.t13].x1}==>{allSteps[bd.t13].y2},{allSteps[bd.t13].x2}==>{allSteps[bd.t13].y3},{allSteps[bd.t13].x3},方向:{allSteps[bd.t13].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 14步:{allSteps[bd.t14].y1},{allSteps[bd.t14].x1}==>{allSteps[bd.t14].y2},{allSteps[bd.t14].x2}==>{allSteps[bd.t14].y3},{allSteps[bd.t14].x3},方向:{allSteps[bd.t14].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 15步:{allSteps[bd.t15].y1},{allSteps[bd.t15].x1}==>{allSteps[bd.t15].y2},{allSteps[bd.t15].x2}==>{allSteps[bd.t15].y3},{allSteps[bd.t15].x3},方向:{allSteps[bd.t15].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 16步:{allSteps[bd.t16].y1},{allSteps[bd.t16].x1}==>{allSteps[bd.t16].y2},{allSteps[bd.t16].x2}==>{allSteps[bd.t16].y3},{allSteps[bd.t16].x3},方向:{allSteps[bd.t16].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 17步:{allSteps[bd.t17].y1},{allSteps[bd.t17].x1}==>{allSteps[bd.t17].y2},{allSteps[bd.t17].x2}==>{allSteps[bd.t17].y3},{allSteps[bd.t17].x3},方向:{allSteps[bd.t17].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 18步:{allSteps[bd.t18].y1},{allSteps[bd.t18].x1}==>{allSteps[bd.t18].y2},{allSteps[bd.t18].x2}==>{allSteps[bd.t18].y3},{allSteps[bd.t18].x3},方向:{allSteps[bd.t18].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 19步:{allSteps[bd.t19].y1},{allSteps[bd.t19].x1}==>{allSteps[bd.t19].y2},{allSteps[bd.t19].x2}==>{allSteps[bd.t19].y3},{allSteps[bd.t19].x3},方向:{allSteps[bd.t19].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 20步:{allSteps[bd.t20].y1},{allSteps[bd.t20].x1}==>{allSteps[bd.t20].y2},{allSteps[bd.t20].x2}==>{allSteps[bd.t20].y3},{allSteps[bd.t20].x3},方向:{allSteps[bd.t20].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 21步:{allSteps[bd.t21].y1},{allSteps[bd.t21].x1}==>{allSteps[bd.t21].y2},{allSteps[bd.t21].x2}==>{allSteps[bd.t21].y3},{allSteps[bd.t21].x3},方向:{allSteps[bd.t21].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 22步:{allSteps[bd.t22].y1},{allSteps[bd.t22].x1}==>{allSteps[bd.t22].y2},{allSteps[bd.t22].x2}==>{allSteps[bd.t22].y3},{allSteps[bd.t22].x3},方向:{allSteps[bd.t22].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 23步:{allSteps[bd.t23].y1},{allSteps[bd.t23].x1}==>{allSteps[bd.t23].y2},{allSteps[bd.t23].x2}==>{allSteps[bd.t23].y3},{allSteps[bd.t23].x3},方向:{allSteps[bd.t23].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 24步:{allSteps[bd.t24].y1},{allSteps[bd.t24].x1}==>{allSteps[bd.t24].y2},{allSteps[bd.t24].x2}==>{allSteps[bd.t24].y3},{allSteps[bd.t24].x3},方向:{allSteps[bd.t24].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 25步:{allSteps[bd.t25].y1},{allSteps[bd.t25].x1}==>{allSteps[bd.t25].y2},{allSteps[bd.t25].x2}==>{allSteps[bd.t25].y3},{allSteps[bd.t25].x3},方向:{allSteps[bd.t25].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 26步:{allSteps[bd.t26].y1},{allSteps[bd.t26].x1}==>{allSteps[bd.t26].y2},{allSteps[bd.t26].x2}==>{allSteps[bd.t26].y3},{allSteps[bd.t26].x3},方向:{allSteps[bd.t26].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 27步:{allSteps[bd.t27].y1},{allSteps[bd.t27].x1}==>{allSteps[bd.t27].y2},{allSteps[bd.t27].x2}==>{allSteps[bd.t27].y3},{allSteps[bd.t27].x3},方向:{allSteps[bd.t27].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 28步:{allSteps[bd.t28].y1},{allSteps[bd.t28].x1}==>{allSteps[bd.t28].y2},{allSteps[bd.t28].x2}==>{allSteps[bd.t28].y3},{allSteps[bd.t28].x3},方向:{allSteps[bd.t28].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 29步:{allSteps[bd.t29].y1},{allSteps[bd.t29].x1}==>{allSteps[bd.t29].y2},{allSteps[bd.t29].x2}==>{allSteps[bd.t29].y3},{allSteps[bd.t29].x3},方向:{allSteps[bd.t29].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 30步:{allSteps[bd.t30].y1},{allSteps[bd.t30].x1}==>{allSteps[bd.t30].y2},{allSteps[bd.t30].x2}==>{allSteps[bd.t30].y3},{allSteps[bd.t30].x3},方向:{allSteps[bd.t30].dir},连步数:{bd.linkStepCnt}");
            Console.WriteLine($"第 31步:{allSteps[bd.t31].y1},{allSteps[bd.t31].x1}==>{allSteps[bd.t31].y2},{allSteps[bd.t31].x2}==>{allSteps[bd.t31].y3},{allSteps[bd.t31].x3},方向:{allSteps[bd.t31].dir},连步数:{bd.linkStepCnt}");

        }
        static Int64 hashKey;
        /// <summary>
        /// 获取对称盘面hash值。包括盘面和落点。
        /// </summary>
        /// <param name="map"></param>
        /// <param name="col"></param>
        /// <param name="row"></param>
        /// <param name="symmetryMode"></param>
        /// <returns></returns>
        public static Int64 GetSymmetryHash(byte[] map, int row, int col,Sym symmetryMode)//各种对称和本身的hash标识。
        {
            //symmetryMode 对称模式
            //0:SELF 本身//1 LR 垂直轴对称//2 UD 水平轴对称//3 SR 捺轴对称//4 SL 撇轴对称//5 CENTER 中心对称//6 L90 逆时针90度//7 R90 顺时针90度
            hashKey &= 0;  
            //Int64 test = 0;
            for (int i = 0; i <= 6; i++)//1-7行//优化掉判断和循环。
            {
                //N*N矩阵,二维转一维后,一维下标 = 行索引 * N + 列索引
                if (symmetryMode == Sym.SELF) //{ line |= ( map[i]     [j]        <<j );  }//本身

                { hashKey |= (map[i * 7+0]) | ((map[i * 7 + 1]) << 1) | ((map[i * 7 + 2]) << 2) | ((map[i * 7 + 3]) << 3) | ((map[i * 7 + 4]) << 4) | ((map[i * 7 + 5]) << 5) | ((map[i * 7 + 6]) << 6); }

                else if (symmetryMode == Sym.SR) //{ line |= ( map[j]     [i]        <<j );  }//捺轴对称

                { hashKey |= (map[0 * 7 + i]) | ((map[1 * 7 + i]) << 1) | ((map[2 * 7 + i]) << 2) | ((map[3 * 7 + i]) << 3) | ((map[4 * 7 + i]) << 4) | ((map[5 * 7 + i]) << 5) | ((map[6 * 7 + i]) << 6); }

                else if (symmetryMode == Sym.LR) //{ line |= (map[i][6 - j] << j); }//垂直轴对称

                { hashKey |= (map[i * 7 + 6]) | ((map[i * 7 + 5]) << 1) | ((map[i * 7 + 4]) << 2) | ((map[i * 7 + 3]) << 3) | ((map[i * 7 + 2]) << 4) | ((map[i * 7 + 1]) << 5) | ((map[i * 7 + 0]) << 6); }

                else if (symmetryMode == Sym.L90)// { line |= (map[6 - j][i] << j); }//逆时针90度
#pragma warning disable CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符
                { hashKey |= (map[6 * 7 + i]) | ((map[5 * 7 + i]) << 1) | ((map[4 * 7 + i]) << 2) | ((map[3 * 7 + i]) << 3) | ((map[2 * 7 + i]) << 4) | ((map[1 * 7 + i]) << 5) | ((map[0 * 7 + i]) << 6); }

                else if (symmetryMode == Sym.UD) //{ line |= (map[6 - i][j] << j); }//水平轴对称

                { hashKey |= (map[(6 - i) * 7 + 0]) | ((map[(6 - i) * 7 + 1]) << 1) | ((map[(6 - i) * 7 + 2]) << 2) | ((map[(6 - i) * 7 + 3]) << 3) | ((map[(6 - i) * 7 + 4]) << 4) | ((map[(6 - i) * 7 + 5]) << 5) | ((map[(6 - i) * 7 + 6]) << 6); }

                else if (symmetryMode == Sym.R90) //{ line |= (map[j][6 - i] << j); }//顺时针90度

                { hashKey |= (map[0 * 7 + 6 - i]) | ((map[1 * 7 + 6 - i]) << 1) | ((map[2 * 7 + 6 - i]) << 2) | ((map[3 * 7 + 6 - i]) << 3) | ((map[4 * 7 + 6 - i]) << 4) | ((map[5 * 7 + 6 - i]) << 5) | ((map[6 * 7 + 6 - i]) << 6); }

                else if (symmetryMode == Sym.SL) //{ line |= (map[6 - j][6 - i] << j); }//撇轴对称  

                { hashKey |= (map[6 * 7 + 6 - i]) | ((map[5 * 7 + 6 - i]) << 1) | ((map[4 * 7 + 6 - i]) << 2) | ((map[3 * 7 + 6 - i]) << 3) | ((map[2 * 7 + 6 - i]) << 4) | ((map[1 * 7 + 6 - i]) << 5) | ((map[0 * 7 + 6 - i]) << 6); }

                else if (symmetryMode == Sym.CENTER) //{ line |= (map[6 - i][6 - j] << j); }//中心对称

                { hashKey |= (map[(6 - i)*7+6]) | ((map[(6 - i)*7+5]) << 1) | ((map[(6 - i) * 7 + 4]) << 2) | ((map[(6 - i) * 7 + 3]) << 3) | ((map[(6 - i) * 7 + 2]) << 4) | ((map[(6 - i) * 7 + 1]) << 5) | ((map[(6 - i) * 7 + 0]) << 6); }




                hashKey = hashKey << 8;
            }
            //落点也加入hash标识中。

            if (symmetryMode == Sym.SELF) {  hashKey |=   ((byte)row << 4) | (byte)col;  }//本身

            else if (symmetryMode == Sym.SR) { hashKey |= ((byte)col << 4) | (byte)row; }//捺轴对称

            else if (symmetryMode == Sym.LR) { hashKey |= ((byte)row << 4) | (byte)(6 - col); }//垂直轴对称

            else if (symmetryMode == Sym.L90) { hashKey |= ((byte)(6 - col) << 4) | (byte)row; }//逆时针90度

            else if (symmetryMode == Sym.UD) { hashKey |= ((byte)(6-row) << 4) | (byte)col;  }//水平轴对称

            else if (symmetryMode == Sym.R90) { hashKey |= ((byte)col << 4) | (byte)(6 - row); }//顺时针90度

            else if (symmetryMode == Sym.SL) { hashKey |= ((byte)(6-col) << 4) | (byte)(6-row); }//撇轴对称  

            else if (symmetryMode == Sym.CENTER) { hashKey |= ((byte)(6-row) << 4) | (byte)(6-col);  }//中心对称

            return hashKey;
        }

        public enum Sym//对称模式枚举
        {
            SELF,    //0: SELF 本身
            LR,      //1 LR垂直轴对称Left-right
            UD,      //2 UD水平轴对称
            SR,      //3 SR 捺轴对称
            SL,      //4 SL 撇轴对称
            CENTER,  //5 CENTER中心对称
            L90,     //6 L90 逆时针90度
            R90      //7 R90 顺时针90度
        }
    }
}

参考:
https://blog.csdn.net/qq_36694133/article/details/124332614

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值