华容道问题求解_详细设计(三)之查找算法1_DFS

news2024/10/26 1:23:47

(续上篇)
使用DFS查找算法的原因是因为它符合本人的思考习惯,另外在第一版时用的就是这个方法,后来知道这不是查找这类问题的最好方法。
在前面的概要设计中的框图里描述的方法就是DFS,它可以找到一个解法,时间可能比较快。(不一定,看运气)

DFS算法基本要素分析

DFS 深度优先算法,核心是采用堆栈结构存贮中间过程。其特点是后进先出,从而也决定了其所谓深度优先的特点。即后续情形都是最先处理,因此就会沿着一条路径探索下去,到达终点后,才返回去处理没有处理过的。画出图来,它的过程就像一个触角一样,伸到最远处,再回退继续执行类似的过程,不但探索知道全部探索结束。

堆栈操作分析

 堆栈中应该保存一个当前的布局,如图示:
 这是初始布局
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1e773af73fd346c3ac5d062745e2ddc7.png)
 移动一个棋子如下
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c9e3023e86ef4afa9035c4768c220519.png)
 假设此时不能继续移动,那么应该返回到上一个场景;也就是说在移动棋子之前,初始布局需要入栈,到当前的无路可走状态(假设),需要做出栈处理。出栈后,需要恢复之前的布局,也就是棋子的位置。因此必须要引入当前布局的一个快照,我这里称之为状态,同时为了便于对相同的局面进行判断,也需要引入一个当前局面的指纹作为记录。当然这个指纹可以是一个hash 值。
 经过上述分析,我们要得到当前布局的快照和一个hash 值用来记录这个快照。
 说明:非常容易犯一个错误,就是把当前的布局实例进行入栈操作,这个看起来好像很对,但是实际上由于入栈操作对对象而言,保存的是一个引用,因此后续操作会影响堆栈里的那个保存的对象属性(其实就是同一个实例而已)。因此必须要克隆一个当前布局的实例,将这个克隆的实例入栈。另外在出栈时,还要将它映射回去到当前的布局中。
 根据上面的思路,有关堆栈相关的代码如下:
 首先,在设置初始布局的函数中的最后部分,进行了堆栈的初始化
     private void _setDefayultLayout()
     {
    	......

         //CurPiece = _gameState.pieces[0];
         // push the initial state onto the  stack 
         SearchOpenPieces(); // 查找可以移动的棋子
         StateShot stateShot = new StateShot(gameState, 0);//克隆当前局面的快照
       
         var stVertexLst = new List<Piece>{ gameState.pieces[2],gameState.pieces[3], gameState.pieces[4], gameState.pieces[5] };//记录可能的起始节点,这里指的是图结构的起点,后面会述及。
         //################################################
         //##                 初始化堆栈                  ##
         //################################################
         
         InitStateStack(stateShot, stVertexLst);//初始化堆栈
     }

为了提高效率,先找到可以移动的棋子,即 open piece,把这个数据也保存到堆栈中,这样出栈后,就不用再计算了。

构造一个图

因为DFS找到的结果不一定是最佳方法,因此要想办法找到求解最佳路径的方法。理论上,上面的算法应该是遍历了所有的场景,如果把这些场景看做是一个节点那么可以想见这个查找方法实际上是可以构造出一个图来的。只要知道起始点和终点,利用现有的最短路径算法,例如 Dijkstra 算法,就可以找到最佳方法(最短路径)。因此下面的问题是如何建立这个图。
图的两个要素,一个是节点一个是路径。显然节点可以考虑用每个布局的hash 值来表示,而路径是两个节点确定的,如果是加权路径,还有个路径的长度。在华容道的经典步骤中,实际上是不考虑路径长度的。只考虑动作,不考虑这个动作的开销,因此在这个图中如果按照传统的手工走法,只要将每条路径的权重设为一样就可以利用最短路径算法找出来和人工移动一致的最佳步数。如果权重不一样,例如将移动一格(一个单位正方形)的权重设为1, 移动两格的权重设为2,利用最短路径算法就可以找出来移动距离最少的解法(有人把这种方法算作是最佳方法)。
构造一个图的具体步骤,在堆栈的进出过程中,有一步是必须要做的,就是重复步骤的判断。可以想见,如果移动时,碰到重复的走法,这个走法应该放弃,否则就会陷入死循环当中。用图来考虑,就是这个节点已经存在了,但是路径应该是新的,因此对于重复的布局,只要将路径加上就可以了,这样到全部步骤结束(栈空)时,这个所有节点和路径就都有了,图的数据结构也就建好了。
另外,华容道的解法布局,我们是知道的,即 曹操的在出口的位置就是完成了一个解;然而此时其他棋子的位置我们是不知道,因此在构造图的终点节点时,需要做个特殊判断。
根据上述分析,得到如下代码

    /// <summary>
    ///  Used to create a graph data structure 
    /// </summary>
    /// <returns></returns>
    internal bool CreateGraph()
    {
        //1# pop from the stack for the first step , note:The initial state must be pushed to the stack!!!!!
        if (statesObjStack.Count == 0) return false;
        var lastState = PopState();
        //var lastHashCode=   layoutHashCodeStk.Pop();

        // map it to the current  state 
        MapToCurState(lastState);

        //Check if all of the open pieces have been moved 
        if (gameState.openPieces.Count > 0 && gameState.curOpenIdx < gameState.openPieces.Count)// There are open pieces not moved , move them one by one.
        {
          //移动一个棋子
          // 确定移动的棋子以及移到到哪个空白区域上
            var selOpenPcs = gameState.openPieces[gameState.curOpenIdx];
            var selPcs = selOpenPcs.piece;
            var toPcs = selOpenPcs.MoveToPcs;
          //移动这个棋子
            var dirFrom = MoveToPcs(selPcs, toPcs);
            gameState.selPcs = selPcs;
            //判断这个布局是否重复
            var redundant = RedundantState(gameState);
            StateShot stateShot = new StateShot(gameState, 0);
            //Create the graph data structure 
            //构造这个图
            AddEdgeToGraph(lastState, stateShot);

            if (redundant.Item1)// return to last layout 
            {
                MapToCurState(lastState);
 
            }
           
			//判断是否所有的open pieces已经走完
            if (gameState.curOpenIdx < lastState.openPcsArr.Length)
            {
                //push it again to the stack
                gameState.curOpenIdx++;
                lastState.lastOpenIdx = gameState.curOpenIdx;
                PushState(lastState);
             }
   
            //check if the current is a redundant one, it it is , return to last layout that going to pop the stack 
    
            if (!redundant.Item1)
            {
                //push the current state to stack 
                //SearchOpenPieces(selPcs, dirFrom);
                SearchOpenPieces();
                //StateShot lastShot = new StateShot(lastState, 0);
                stateShot = new StateShot(gameState, 0);
                PushState(lastState, stateShot,selPcs, toPcs);//add edge to the grapph 
            }
            // Judge if it succeeds that caocao is at the exit of the board.
            
         //############################################################################
         //### 判断曹操是否出来,并且计算当前的hash值,记住这个节点,保存到终点集合中。 ###
         //############################################################################
         
            if (selPcs.type == HRDGame.TYPE_BIG_PIECE )
            {
                if (selPcs.hrdPos.X == 2 && selPcs.hrdPos.Y == 4)
                {
                    RefreshLayout();
                    Application.DoEvents();
                    var verTex = GetMyHashCodeV1(gameState);
                    // the layout might not the same that needs to record all of them 
                    if (!endVtxLst.Contains(verTex)) endVtxLst.Add(verTex);
                  }
            }

        }

        return true;

    }

上述程序运行结束后,我们要的图就构造好了,同时也会找到一定数目的解法,这个解法大概率不是最优的。最后调用最短路径算法,就可以找到最佳步骤。
图的构造,采用一个数据字典,定义如下:

       Dictionary<int, (int,int)> hCodeAndShotShortPathDict = new Dictionary<int, (int,int)>();// key:the hash code of the current layout, value.item1, the hashcode of the source layout , item2 the number of the shortest steps.

注意,上面使用了 C# 的tuple类型。字典变量的Value 值的第二项就是路径的权重,这里都设定为1,实际上相当于无权图。
构造图的关键函数代码如下:

       private void AddEdgeToGraph(StateShot source, StateShot dest)
       {
          //计算起终点的hash值
           var toHashCode = GetMyHashCode(dest);
           var frmHashCode = GetMyHashCode(source);
           AddEdge(frmHashCode, toHashCode, 1);
       }

其中

        public void AddEdge(int source, int dest, int weight)
        {
            if (!adjacencyList.ContainsKey(source))
            {
                AddVertex(source);
            }

            if (!adjacencyList.ContainsKey(dest))
            {
                AddVertex(dest);
            }

            if(!adjacencyList[source].Contains((dest, weight))){
                adjacencyList[source].Add((dest, weight));
            }
            if (!adjacencyList[dest].Contains((source, weight)))
            {
                adjacencyList[dest].Add((source, weight));
            }
            //adjacencyList[dest].Add((source,weight)); // Uncomment this line if the graph is undirected
        }

注意这个图是无向图。
另:这个图的构造和Dijkstra解法均来自ChatGPT
图构造好之后,调用 Dijkstra 方法,得到了最优解,这个步骤和公认的81步是一致的,因此这也间接证明了上述方法的正确性。
实际上找到了好几种不同的符合结果的布局,其中最少的一步是 81步。结果截图如下
结果布局1
结果布局1
结果布局2
在这里插入图片描述
结果布局3
在这里插入图片描述
还有几种从略。
简单讨论
由于开局是对称的,因此结果的布局结果应该也是对称的,但是最终笔者的算法结果并不是对称的。思考一下原因,是在求解过程当中,对于重复节点的判断应该导致没有出现对称结果的原因。但是这并不妨碍图的构造。如果将这个图画出来,它应该是一个对称的图形。
最终81步的结果截图如下:
在这里插入图片描述
演示过程如下

华容道81步演示_CSDN

这个是笔者最早找到的方法,是一个综合应用,但是效率不高,时间也比较长。而在采用了BFS之后,不但效率提高很多,也找到了对称的结果集,将在后续篇章介绍。

MaraSun BJFWDQ
2014-03-07

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVS集群 ----------------(直接路由 )DR模式部署

一、LVS集群的三种工作模式 lvs-nat&#xff1a;修改请求报文的目标IP,多目标IP的DNAT lvs-dr&#xff1a;操纵封装新的MAC地址&#xff08;直接路由&#xff09; lvs-tun&#xff1a;隧道模式 lvs-dr 是 LVS集群的 默认工作模式 NAT通过网络地址转换实现的虚拟服务器&…

STM32CubeIDE基础学习-代码编译介绍

STM32CubeIDE基础学习-代码的编译介绍 前言 当写完代码后&#xff0c;即在调试和下载代码之前都是需要对工程代码进行编译的操作&#xff0c;不然是无法正常进行代码调试和下载的&#xff0c;所以编译这一步是一个关键步骤。 下面就来介绍下STM32CubeIDE软件环境的代码编译方…

Spring Boot 3核心技术与最佳实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 highlight: a11y-dark 引言 Spring Boot作为…

C++ · 代码笔记3 · 引用

目录 前言011引用初探_引用与普通变量012引用初探_引用作为函数参数013引用初探_引用作为函数返回值014引用初探_引用返回局部函数造成的错误015引用初探_多级引用020引用与指针递增的区别030const与引用040使用const限定的函数形参引用 前言 本笔记所涉及到的编程环境与 《C …

社区店选址案例分享:从成功案例中汲取经验

想开实体店或创业的朋友们&#xff0c;大家好&#xff01;我是一名鲜奶吧5年的创业者&#xff0c;今天我将以开店专家的角度&#xff0c;为大家分享一些社区店选址的成功案例&#xff0c;希望能给你们带来启发和帮助。 选址是实体店成功的关键之一&#xff0c;而社区店更是要注…

16 进程终止

终止的结果 进程终止后&#xff0c;释放申请的相关内核数据结构和对应的数据和代码。本质就是释放系统资源 终止的场景 代码跑完&#xff0c;结果正确代码跑完&#xff0c;结果不正确代码没有跑完&#xff0c;程序崩溃 进程常见退出方法 正常退出&#xff1a; 1.main返回 …

selenium-java web自动化测试工具抓取百度搜索结果实例

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

高中数学:函数类高阶题目考法汇总(较难)

1、基本性质类问题 2、解抽象函数不等式问题 在1的基础上 解题思路&#xff1a; 根据f的单调性&#xff0c;去f 将问题转化成具体不等式问题。 例题&#xff1a; 对于第三小问&#xff0c;每一个变形前面&#xff0c;都要加上只要证、即证关键词&#xff0c;才不会被扣过程…

【STM32+HAL】姿态传感器陀螺仪MPU6050模块

一、准备工作 有关OLED屏初始化的问题&#xff0c;详见【STM32HAL】OLED显示初始化配置 二、所用工具 1、芯片&#xff1a;STM32F10C8T6 2、CUBEMX配置软件 3、 6 轴运动处理组件MPU6050 三、实现功能 OLED屏显示姿态角 四、HAL配置步骤 1、开启I2C1进行MPU6050通信 2、开…

红队攻击手“实战”特训

伴随着新的一年的到来&#xff0c;我们最新一期的红队攻防&#xff0c;也如约而至~ 每一期我们都会做二次学员反馈&#xff0c;根据同学们的真实反馈和需求&#xff0c;来调整讲师及授课内容 新的一期我们增加了C基础&#xff0c;python基础&#xff0c;汇编基础的课程&#…

LeetCode 刷题 [C++] 第139题.单词拆分

题目描述 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 题目分析 背包问题特征&#xff1a; 是否…

用云手机进行舆情监测有什么作用?

在信息爆炸的时代&#xff0c;舆情监测成为企业和政府决策的重要工具。通过结合云手机技术&#xff0c;舆情监测系统在品牌形象维护、市场竞争、产品研发、政府管理以及市场营销等方面发挥着关键作用&#xff0c;为用户提供更智能、高效的舆情解决方案。 1. 品牌形象维护与危机…

Codeforces Round 928 G. Vlad and Trouble at MIT 【树形DP】

G. Vlad and Trouble at MIT 题意 给定一颗 n n n 个节点的树&#xff0c;每个节点有一个学生&#xff0c;学生有三种类型&#xff1a; 参加派对的 P P P 类型&#xff0c;会制造噪音想要睡觉的 S S S 类型&#xff0c;不希望被吵到有没有噪音都可以的 C C C 类型 噪音会…

VUE3中ArcGIS JsAPI 4.27 Map 隐藏地图黑色边框

问题&#xff1a; vue3中引入arcgis jsapi 地图加载后&#xff0c;点击地图会出现黑色边框&#xff0c;看起来很不协调 解决方案&#xff1a; 新建自定义CSS文件&#xff0c;输入一下样式内容&#xff0c;并在vue页面直接用import引入即可。 注意&#xff1a;直接写到vue页面…

C++复习笔记——泛型编程模板

01 模板 模板就是建立通用的模具&#xff0c;大大提高复用性&#xff1b; 02 函数模板 C另一种编程思想称为 泛型编程 &#xff0c;主要利用的技术就是模板 C 提供两种模板机制:函数模板和类模板 函数模板语法 函数模板作用&#xff1a; 建立一个通用函数&#xff0c;其函…

【洛谷 P8682】[蓝桥杯 2019 省 B] 等差数列 题解(数学+排序+差分)

[蓝桥杯 2019 省 B] 等差数列 题目描述 数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列&#xff0c;只记得其中 N N N 个整数。 现在给出这 N N N 个整数&#xff0c;小明想知道包含这 N N N 个整数的最短的等差数列有几项&#xff1f; 输…

Mybatis-Plus:几个好的用法

项目使用mybatis-plus&#xff0c;同事的用法让我很难苟同&#xff0c;于是自己百度了一些用法&#xff0c;分享一下。 一、静态适配器 最好使用官方提供的ObjectUtils,比较全面&#xff0c;不用加载不同的对象工具。 com.baomidou.mybatisplus.core.toolkit /*** 设置查询适配…

C# 由左上、右下两个坐标点计算矩形的长、宽以及两点的距离

一、计算长、宽 直接使用坐标点计算 // 定义矩形左上角和右下角的坐标 Point topLeft new Point(0, 0); Point bottomRight new Point(5, 10); // 计算矩形的长和宽 int width bottomRight.X - topLeft.X;//矩形宽度 int height bottomRight.Y - topLeft.Y;//矩形高度或是…

实战-Sealos一键部署k8s集群-2024.3.7(测试成功)

目录 [toc] 原文链接 实战-Sealos一键部署k8s集群-2024.3.7(测试成功) | 彦 推荐文章 我的开源项目&#xff1a; 开源项目 | 彦 实验环境 centos7.6 1810,5.4.270-1.el7.elrepo.x86_64sealos v5.0.0-beta4k8s v1.28.7 &#xff08;当前时间&#xff1a;2024年3月7日 k8s最新版…

ruoyi-vue框架密码加密传输

先看一下改造后的样子&#xff0c;输入的密码不会再以明文展示。 下面我主要把前后端改造的代码贴出来。 1.后端代码 RsaUtils类 在com.ruoyi.common.utils包下新建RsaUtils类&#xff0c;RsaUtils添加了Component注解 generateKeyPair()构建密钥对添加了Bean注解 在项目启动…