连连看设计之核心算法

  前段时间做了一个小型的连连看游戏,基本功能都实现了,做完后对里面的用到的算法印象深刻,特分享出自己的思维过程,我尽量还原自己的想法,由于我算法能力不强可能更倾向于普通大众的水平,所以可能很多人都会有启发 :).

 

 

 

  简单的连连看游戏基本界面如上图,这个问题来源于编程之美的1.14题,它要求我们设计出这样一个连连看游戏,考察的是里面最最基本的算法知识,当然,算法有了,写出这样一个游戏来本身不是很困难的事情。

  先来了解下连连看的规则:

  1 用户可以把两个相同的图用线连到一起,如果连线拐的弯不超过两个(可以等于)则表示可以消去。

  2 当界面上所有的图片都消去后,则游戏胜利。

  3 游戏的过程中可能出现这样一种情况,即运用规则再也无法消去任何两个图形了,那么可以利用重置来解除死锁。

核心算法 

  这样一个程序从工程性角度讲实现不会太难,关键在里面的算法。最为关键的是寻找最小拐弯的路径来判断两个图形是否可以相消,这是最基本的。利用这个可以轻松地实现解除死锁功能,即循环界面上所有图形判断是否可以相消,如果没有一个可以消去则表示当前处于死锁状态需要重置。

1 怎么样判断可以消去 

       这个问题我当时想了很久,居然卡了半天,后来一想自己曾经做过类似的问题居然没有吸取到以前的经验实在可悲。首先定义问题的类型,这是一个最优化问题,最优化问题的解决方法有几类,大家可能马上会想到什么动态规划或者贪心,其实还有一种方法可以求最优化问题,即广度优先搜索。这是在解决图一类问题时常要考虑到的。 

      这个问题其实就是求最小转弯数是否小于等于2的问题,这是一个最优化问题,那么用广度优先搜索是非常自然的。广度优先搜索有这样一个特性,由于是从中心节点一层一层往外遍历,那么最先符合的肯定是离中心节点最近的,这是广度优先搜索可以被用来求解最优化问题的关键。还有一个要考虑到的就是广度优先搜索有个搜索目标,我们这里需要换成拐弯数,这样便可以求得最小值。

   接下来就要理解怎么样按转弯数来搜索。很简答的思考方式是从简单特例开始思考,首先我定义-1的转弯数是自己。那么0 呢,由于界面是方格棋盘式,所以是从本身出发上下左右四个方向的空格子,见下图:

 

      蓝色方块表示还没有消去的格子,白色的格子为已经消去的,那么可知所有的空白格子都是转弯数为0的。

      基于这个基本的认识可以定义一次基本的广度优先搜索:分别搜索上下左右四个方向,如果遇到空白格子且未在队列中都入队列,直到遇到未消去的蓝色格子则该方向的搜索结束,转入下个方向。四个方向完成后,即完成一次基本的广度优先搜索。(不让顶点重复入队列是防止死循环,同时由于已经如果队列的顶点其周围的顶点都被搜索过就没有必要再入)。

      有了基本的广度优先搜索后我们可以定义这样一个判断是否可以消去的算法:

      假设当前选中的两个格子为first,second,每个格子到first的最小转弯数为minCrossNum。初始first的minCrossNum设为-1.其他所有格子的minCrossNum都设为无穷大,即int32.maxvalue。

  1 将first入队列

  2 从队列取出队头元素并设为currentGrid,向四周(上下左右)做广度优先搜索。

搜索方法:遇到空格子(已经消去后剩下的)如果没访问过就更新该格子的minCrossNum,让minCrossNum+1,如minCrossNum小于等于2就将其入队列,直到遇到第一个不为空的格子为止。此时判断此格子是否可以和second消去,如果可以,则退出,重置所有格子状态(minCrossNum),否则退出此方向的搜索,转至下一方向。.

  3 若四个方向广度优先搜索后未发现可以直接消去的,重复操作2,直到队列为空则返回false表示不存在可以消去的。

2 求取最小转弯的路径 

       有了判断消去的算法后,就可以来求路径。求路径的思考方法可以这样想,因为我们是从first开始搜索,那么如果可以消去,最后一个顶点一定是second顶点。(first为第一个点击的顶点,second为第二个点击的顶点)。这样我们可以反向来求取路径,即从second来求。那么什么样的顶点会是最后路径上的点呢?我们要找的是转弯的点,这些是关键点,有了这些关键点后,直接在两两之间划直线即可恢复路径。所以我们要在搜索路径的时候记录这些关键的转弯点。我们给每个顶点加一个属性,ConnectFrom。这样一个属性记录当前顶点是从哪个顶点广度优先搜索来的,即每个基本广度优先搜索的中心顶点,见下图:

  在上图中,红色的表示first和second两个顶点,左上角是first。蓝色表示拐弯路径是0的顶点,黄色表示拐弯数1的顶点,而粉红色便是非常关键的路径关键点(我自己取的名字),我们在搜索过程中要记录的是每个顶点的这样的关键点,比如second是由粉红色顶点扩展而来的。

       于是我们有了获取路径的算法:

       在每个Grid中设置一个属性,connectFrom用于记录该Grid的上一个关键点。同样假设当前选中的两个格子为first,second,路径队列为PathQueue(初始为空)

       1 在消去算法的第二步更新currentGrid的minCrossNum属性时把它的connectGrid属性设为队头即转弯开始的点,在得到可以消去结果时设置second的connectFrom属性为当时的队头。

       2 求取过程:赋值currentGrid为second,currentGrid入队列PathQueue,同时currentGrid赋值为currentGrid.connectFrom,重复步骤2直至currentGrid为first。

       3 first入队列PathQueue。

       最终,PathQueue即存储了所有的关键点,绘制时只需要用GDI+的绘制直线API即可。

       以上便是关于连连看核心算法的设计包括了消去算法以及路径求取算法,在算法的思考过程中,我们应该深刻认识到广度优先搜索的应用方法在大脑中建立这样一个场景,以后求解类似问题便能心中有数。

转载于:https://www.cnblogs.com/HappyAngel/archive/2010/12/25/1916731.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值