马踏棋盘(构思)

这学期数据结构有这么一题,我感觉蛮有意思,就做了下。下面分享一下我的收获。 


以下是我编程前做的工作:

1.弄清题目的意思

2.提出问题(做的过程中可能遇到的问题)

3.构思解决这些问题的大致方法(与编程语言(工具)联系起来),并考虑如何将这些方法在程序流程中联系起来

4.设计出程序流程图

5.优化解决方法、程序流程

6.开始编程

        在编程过程中经常会冒出新的问题,或是想到更好的方法,到时候再改就可以了。不过如果编程前的构思不完备的话,不仅编程时思路不连贯,而且需要大幅度修改程序的概率大大增加,会浪费更多时间。

1.弄清题目意思

题目描述:国际象棋中,一只马在8*8格子的棋盘中走,不能重复走已经走过的格子,要求马把每个格子都走过。 


(如果马处在绿色的位置,那么他可以往灰色位置上走)


2.提出问题

下面是我想到的问题:

(1)如何表示马的位置和棋盘上的格子?

(2)如何让马按照国际象棋的规则来走(隔一个格子走)?

(3)如何用C语言模拟马的行走路线(前进和退步)?

(4)如何判断下一步的位置是否出界、是否是已经踩过的位置?

(5)下一步往哪走?

(6)当马走到某个位置,发现没有可走的路了,该怎么办?


3.问题的大致解决方法


问题(1)的解决方法:

用坐标表示马和棋盘的各个位置。我的坐标系是这样建立的:

问题(2)如何让马按照国际象棋的规则来走?

对于第二个问题,教材上给了一种非常方便的方法,定义两个数组,我的程序里是

int xCoo[8] = {-2,-1,1,2,2,1,-1,-2};//xCoo 表示 x Coordinates,x坐标
int yCoo[8] = {1,2,2,1,-1,-2,-2,-1}; //y坐标

        有了这两个数组,假如马当前的位置是(x,y),那它下一步可走的坐标就可以是( x+xCoo[i] , y+yCoo[i] ),i=0~7。

但是这样计算得到的坐标有可能超出棋盘的边界,或者,得到的坐标马可能已经走过,就像问题(4)所说的那样。


问题(3)如何用C语言模拟马的行走路线(前进和退步)?

        马走每走一步,路线长度就加1,每退一步,路线长度减少1。而且退回的时候,路线上减少的不是马的第一步,而是退回之前路线中的最后一步,这和数据结构中的栈的规则是一致的,也就是“后进先出”的原则。所以可以用栈来模拟。那是用顺序栈呢,还是用链栈呢?顺序栈一次性分配内存,链栈可动态改变栈的大小。这里我感觉差别不大,不过我用了链栈。栈的节点暂时可以这样定义:

struct THREAD

{

    int x;//当前节点的x坐标

    int y;//当前节点的y坐标

    struct THREAD  *prev;//保存指向前一个节点的指针

}

问题(4)如何判断下一步的位置是否出界、是否是已经踩过的位置?

        我打算用两个函数来解决这个问题,isOut 函数判断是否出界,isStepped 函数判断是否是走过的格子。这里又引出了一个问题:如何让马记忆已经走过的格子的坐标?我最初没有记忆,用的是遍历栈的方法判断。后来发现效率太低,便用了一个二维数组来记忆:char stepped[8][8],每个元素初始化为'n',表示没有走过。一旦马走过了某个格子,比如(x,y),则执行:stepped[ x-1 ][ y-1 ] = 'y'; 表示(x,y)已踩过。这样的话,调用isStepped函数时,想知道(x,y)是否被走过,只要

if( stepped[ x-1 ][ y-1 ] == 'y')

    return 1;//表示被踩过了

else

    return 0;//表示没被踩过

这样就可以了。事实上我用了一个动态二维数组,因为我做的棋盘大小是可变的害羞


问题(5)下一步往哪走?

        这个问题我最初设计的时候没有仔细考虑,用的是随机选取格子的方法。因为我当初没有想到让马选一条路有这么多种可能性,所以后来让他随机走的时候,走半个多小时都找不出一条路,于是我只好改程序,让马在选路的时候也要“思考”。

        这里又有个隐含的问题:选格子的时候,选的是可以走的格子,是通过isOut、isStepped函数计算出来的,那么这些可走的格子的坐标保存在哪里呢?

        由于马到了每个格子后,可走的格子都不一样,所以马每到一个位置,我就计算可走的格子的坐标,并保存到当前位置对应的节点(当前栈顶)中。可这是一组坐标,最多可能有8个坐标,全都保存到一个节点中吗?我没有这样做,因为这样显得节点中数据有点乱,我不喜欢。。。于是我就定义了一个结构体struct CANGO,结构中包含x、y坐标,保存的时候申请一块8*sizeof(CANGO)大小的堆内存,也就是一个结构体数组,然后在栈的节点中保存指向这个结构体数组首地址的指针。


struct CANGO

{

    int x;

    int y;

}

struct THREAD

{

    int x;//当前节点的x坐标

    int y;//当前节点的y坐标

    struct CANGO *pCango;//保存当前节点可走的格子的坐标

    struct THREAD  *prev;//保存指向前一个节点的指针

}

......

struct THREAD *pNew;

pNew->pCango = (struct CANGO *)malloc( 8 * sizeof( struct CANGO ) );

......

        CANGO结构数组中有8个元素,如果当前节点可走的格子少于8个,比如可走的只有5个,那就把这5个格子的坐标保存到CANGO结构数组的其中5个元素中,其他3个元素的x、y成员都赋值为-1,来表示这些位置是不可走的(出界或者是已经走过)。

赋予马思考的能力

        怎么降低马走错退回的概率呢?如果让马都往空旷的地方走,到后面更容易出现这种情况:没走过的格子被走过的格子包围而导致马无法跳到没走过的格子上,于是马又得退回重新找路,因此效率会很低!


        所以要往这样的位置走:从这个位置上看,马可能走的位置数最少。也就是说,假如马的位置是A,下一步可走的位置有B1、B2、B3、B4,从B1出发,可走的位置有5个,从B2出发可走的位置有3个,从B3出发可走的位置有4个,从B4出发,可走的位置只有1个,那么下一步应该选择B4。

        那如果还有一个B5,可走的位置也只有1个,那应该选哪个呢?我的做法是选择B4、B5中更靠近边界的那一个。更靠近边界是什么意思?比如B4的坐标是(8,1),B5的坐标是(2,5),那就选B4。因为B4离最近边界的距离是1,而B5离最近边界的距离是2。

        如果从A出发还有一个可走的坐标B6:(8,5),这个坐标可走的位置也只有1个,并且到最近边界的距离也是1,那该选哪个呢?我让马选更靠近角落的那个,也就是B4(8,1)。

        要是出现同样少的可走位置、同样靠近边界、同样靠近角落,那就让它随机选一个走吧。。。

以上这些想法也是受网上所说的“贪心算法”启发,这样程序的效率大大提高。

问题(6)当马走到某个位置,发现没有可走的路了,该怎么办?

        很简单,退回。不过退回以后的工作就不是这么简单了,我第一次做的时候就犯了很大的错误,退回以后,马又重新走已经错过的路了,结果导致无限循环。。。。后来我改了,我跟马说,你从哪里跳回来,就不要再跳回去了。他的确这么做了,可是还是有问题,你觉得这是为什么?我来解释一下,如果马当前位置是A,他去了B,发现没路走了,退回到A,他下一步不会再去B了,然后从A跳到C,发现在C也没路可走,于是 又退回到A,照我所说的,下一步他不会去C了,然后他又 看见了B,于是又去B了,还是走了重复的路。(注意我这次的做法是:从哪里跳回来,就不要再跳回去,所以会出现这种现象)。

        那应该怎么办呢?难道从A到B不可行,那应该记住永远不能从A到B吗?我认为有这种可能:马从A退回,退啊退,绕了别的路,又到了A,这时候从A到B,发现有路可以走了。

        看下图中的例子,假设马走啊走走到D,沿橙色的路线D->G->H->A->B走,走到B发现没路了退回到A,到A没路又退回,一直退又退到回了D,选择了另外那条绿色的路线D->E->F->A->B,这时候从B就可以到G了。


       这看上去好像有个问题,因为假如可以从绿色的路线的B到G,那么马之前沿橙色路线退回的时候,退到G就有可能的路线G->B了,因此不可能再从G退到D然后又选择绿色的路线。

        但事实上还有一种情况:马确实从B退到A又退到H又退到G,然后在G发现可以去B,到了B发现只能去A,但是到了A却发现没路可走,又退回到B,然后又退到了G,如果到了G他发现没路可走,就会从G退回到D了!然后他走了绿色的路,D->E->F->A->B,到了B发现可以去G耶!不过这时候,到了G,它肯定没有路可以走了。为什么?因为能沿绿色的路走的前提是他退到了D,要退到D,那必须是在G发现没有路走了。

        所以,并不是一旦发现从A到B路没有路,以后从A到B就一定没有路。所以我觉得,不能因为从A到B不行,就把这步路永久地记下来,以后都不能走。因为即使从B到了G没有路了,G也有可能是最后一步呀,这样的话,如果规定不能从A到B,马很有可能会做出误判,告诉你所有路都走遍了,没有一条可行的路,而事实上可能是有的。

        前面我说过,如果只告诉马“从哪里跳回来,就不要跳回去”,会导致死循环(A->B->A->C->A->B......),又不能永久规定不能从A到B,那该怎么办?我的办法是暂时地保存A不能去的位置。比如A到B不行,那就在A的节点中保存B的位置,以后不往B走;A到C不行,在A中保存C的位置,以后不往C走。那不是成永久保存了吗?不是的。当马在A发现没路可走的时候退回,A节点被free,保存的B和C的位置也会被释放。如果下一次又走到A,重新构建A节点,这时A里没有保存任何不能走的坐标位置。

        这东西太复杂了。。。不知道我分析得对不对,如果感觉我讲错了,一定要跟我说呀!!

        保存的不可走的位置的方法和保存可走位置的方法一样,在节点中保存指向struct CANGO 结构体数组的指针,但是为了好区分,我做了一个宏定义:

#define CANTGO CANGO

对struct THREAD结构的修改如下:

struct THREAD

{

    int x;//当前节点的x坐标

    int y;//当前节点的y坐标

    struct CANGO *pCango;//保存当前节点可走的格子的坐标

    struct CANTGO *pCantgo//保存当前节点不可走的格子的坐标

    struct THREAD  *prev;//保存指向前一个节点的指针

}


4.设计程序流程

我直接上图了。。。


具体实现的话以后再整理给大家!


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值