例题3-2,蛇形填数,解题报告

作者[email protected] 2011年2月1日 21:09:28

本题,是《算法竞赛入门经典》(刘汝佳)的一道例题。(P35,例题3-2)

题目描述如下:(我改进了一点点,使之叙述得更加严密。)

问题描述

在n*n的方阵里填入1,2,3,...,n*n,要求填成蛇形。

1<=n<=19

由于在标准的命令行界面下,每个屏幕大约只有25行,所以不必让n具有特别大的值。

当然如果你采用重定向技术,把结果输出到文件中,那么就没有这种局限性了。

假设用户输入的数据都是合法的。

输入:1个整数n

输出:蛇形填数的结果;每个数字占2个字符的位置,各数字之间用1个空格隔开

例如

4

10 11 12 1

9 16 13 2

8 15 14 3

7 6 5 4

=============================================

本题,利用一个二维数组来存储和输出计算的结果,看起来是一个“很自然”的解法。

我的解题策略描述如下:
把整个数组想象成一幅地图。有一个机器人从地图的左上角开始,按照蛇形填数的方案行走。每走一步,机器人就会在当前位置填上自己的当前步数。如此,当机器人走了n*n步之后,蛇形填数即被完成。

显然,可以用一个二维数组来表示这幅地图。同时,二维数组的2个下标分别可以机器人所在地位置。

看起来,代码片段是这样的:

0001 # define MAX (10)   /*地图的行/列上限*/

0002 # define BRICK (-1) /*砖块:表示机器人不能走到该位置*/

0003 # define BLANK (0)  /*空白:表示机器人可以走到该位置*/

0004 int map[MAX][MAX],      /*地图*/

0005         n,      /*机器人一共要走n*n步*/

0006         i,j;

0007 /*初始化地图:先全部铺上砖块*/

0008 for (i=0; i<=MAX-1; i=i+1) {

0009     for (j=0; j<=MAX-1; j=j+1) {

0010         map[i][j]=BRICK;

0011     }

0012 }

0013 /*初始化地图:再挖出需要蛇形填数的区域*/

0014 scanf("%d",&n);

0015 for (i=1; i<=n; i=i+1) {

0016     for (j=1; j<=n; j=j+1) {

0017         map[i][j]=BLANK;

0018     }

0019 }

例如上面的代码,给出的地图看起来像是这样的:

0001 /*打印机器人走出的地图*/

0002 for (i=0; i<MAX; i=i+1) {

0003     for (j=0; j<MAX; j=j+1) {

0004         printf("%3d ",map[i][j]);    /*每个输出数据占3个字符的宽度*/

0005     }

0006     printf("\n");    /*每行的结尾打印一个回车换行标记*/

0007 }

image

绿色的区域,就是我们要让机器人在其中按照“蛇形填数”的要求行走的区域。

蓝色的线条和箭头,意指机器人行走的路线和方向。

粉色的区域,是不可到达的位置。我们用砖块(BRICK)填满这些地方。

由上图可见,我们总是可以用二维数组的2个下标(整数)来表示机器人的当前位置。比如,机器人一开始的位置位于map[1][6];该处的值应当为1,意指机器人在这个位置落下的是第1步。

策略的关键是:如何在已知机器人当前位置的情况下,判定出机器人下一步应该要走到哪个位置?

为此,我采用了如下的判定策略表:

========================================================
        当前可行动方向      |         下一步方向
========================================================
        只能向左              |         向左
        只能向右              |         向右
        只能向上              |         向上
        只能向下              |         向下
        向左或向下           |         向下
        向左或向上           |         向左
        向右或向上           |         向上
        向右或向下           |         向右
========================================================

一旦根据上述“判定策略表”做出了“下一步方向”,就应当“立即行动”,不能再看“判定策略表”中的其他项。举例说明:如果机器人采用了“只能向右,则向右”这一策略,那么就应当立刻走出这一步;而不应当继续其他的行动策略。关于这点的详细说明,请看源代码。

总而言之,当机器人面临着“下一步往哪儿走”的选择时,就可以根据上面的“判定策略表”作出抉择。而据此表所作出的选择,确保了机器人总是能沿着“蛇形填数”的方向走下去。

几个细节的说明:

1. # define MAX (10) /*地图的行/列上限*/ 限定了地图的大小。

2. # define BRICK (-1) /*砖块:表示机器人不能走到该位置*/ 事实上,如果map中某个位置的元素值只要不是BLANK(空白),机器人就不能走到那个位置。初始化时,将其值设定为BRICK(-1)是为了判断地图边界的时候比较方便。

3. 地图的最上方1行和最左端1列,都是没有使用的空间。我在里面填入BRICK,这样处理,会比较方便地判定机器人是否走到了地图的边界。

程序的源代码,在这里:

http://cid-bfe8af46e42e3ecf.office.live.com/self.aspx/%e7%ae%97%e6%b3%95%e7%ab%9e%e8%b5%9b/%e6%ba%90%e4%bb%a3%e7%a0%81/%e4%be%8b%e9%a2%983-2%ef%bc%8c%e8%9b%87%e5%bd%a2%e5%a1%ab%e6%95%b0.c

最后,我给出整个程序的控制流程图如下:

http://qdjxzg.blu.livefilestore.com/y1pzwJasopUB_HGeR-USsvn0lE1d9-2r9K-F6OIYzZToqixcMYyBROCJItEScq-nq76ghR14B3b3lZckQP9n9OC6sOHbEhguHFY/%E4%BE%8B%E9%A2%983-2%EF%BC%8C%E8%9B%87%E5%BD%A2%E5%A1%AB%E6%95%B01.png?psid=1

例题3-2,蛇形填数1

(说实话,这个流程图挺复杂的,看起来有点儿恶心。以后我们学会了函数,这个代码写起来,就会好看多了。)

小结

1. 合适的数据表达形式(数据结构)是解决问题的关键。比如在这里,利用二维数组来表达地图,是“很自然”的解法。

2. 合理的解题策略,往往不止一个。比如书中的解题策略当然也是正确的。但我却很难理解其中的逻辑(悟性较差)。寻找自己所擅长理解的逻辑,也正是提高自己看问题水平的一个重要方面。

各位同学,如果你觉得你已经看懂了上述内容;那么,

请思考下面2个问题

  1. 我的解题方案有什么不妥:在逻辑上有否漏洞?在代码上有否错误?你可否进一步提高代码的可读性或是效率?
  2. 本题中“蛇形”是顺时针的;你可否给出一个“逆时针方向的蛇形填数”?把你的代码贴到群里面。

谢谢观赏!

==========================================================

完整的源代码如下:

0001 /*

0002 例题3-2,蛇形填数.c

0003 20:49 2011年2月1日

0004 问题描述:

0005     在n*n的方阵里填入1,2,3,...,n*n,要求填成蛇形。

0006     1<=n<=19

0007     由于在标准的命令行界面下,每个屏幕大约只有25行,所以不必让n具有特别大的值。

0008     当然如果你采用重定向技术,把结果输出到文件中,那么就没有这种局限性了。

0009     假设用户输入的数据都是合法的。

0010 输入:1个整数n

0011 输出:蛇形填数的结果;每个数字占3个字符的位置,各数字之间用1个空格隔开

0012 例如:

0013 4

0014 10 11 12  1

0015  9 16 13  2

0016  8 15 14  3

0017  7  6  5  4

0018 解题策略:(详见“例题3-2,蛇形填数,解题报告.pdf”)

0019     把整个数组想象成一幅地图。有一个机器人从地图的左上角开始,按照蛇形填数的方案行走。每走一步,机器人就会在当前位置

0020     填上自己的当前步数。如此,当机器人走了n*n步之后,蛇形填数即被完成。

0021     显然,可以用一个二维数组来表示这幅地图。同时,二维数组的2个下标分别可以机器人所在地位置。

0022     所以,我们总是可以用二维数组的2个下标(整数)来表示机器人的当前位置。

0023     而该策略的关键是:如何在已知机器人当前位置的情况下,判定出机器人下一步应该要走到哪个位置?

0024     为此,我采用了如下的判定策略表:

0025     ========================================================

0026             当前可行动方向      |         下一步方向

0027     ========================================================

0028             只能向左          |         向左

0029             只能向右          |         向右

0030             只能向上          |         向上

0031             只能向下          |         向下

0032             向左或向下          |         向下

0033             向左或向上          |            向左

0034             向右或向上          |            向上

0035             向右或向下          |            向右

0036     ========================================================

0037     一旦根据上述“判定策略表”做出了“下一步方向”,就应当“立即行动”,不能再看“判定策略表”中的其他项。

0038     关于这点,请看源代码。

0039     当机器人面临着“下一步往哪儿走”的选择时,就可以根据上面的“判定策略表”作出抉择。而据此表所作出的选择,确保了机器人

0040     总是沿着“蛇形填数”的方向走下去。

0041 */

0042 # include "stdio.h"

0043 # define MAX (100)    /*地图的行/列上限*/

0044 # define BRICK (-1)    /*砖块:表示机器人不能走到该位置*/

0045 # define BLANK (0)    /*空白:表示机器人可以走到该位置*/

0046 int main() {

0047     int map[MAX][MAX],        /*地图*/

0048         robotX=1,robotY,    /*机器人的位置*/

0049         n,                    /*机器人一共要走n*n步*/

0050         i,j,

0051         step=0;                /*当前步数*/

0052 

0053     /*初始化地图:先全部铺上砖块*/

0054     for (i=0; i<=MAX-1; i=i+1) {

0055         for (j=0; j<=MAX-1; j=j+1) {

0056             map[i][j]=BRICK;

0057         }

0058     }

0059     /*初始化地图:再挖出需要蛇形填数的区域*/

0060     scanf("%d",&n);

0061     for (i=1; i<=n; i=i+1) {

0062         for (j=1; j<=n; j=j+1) {

0063             map[i][j]=BLANK;

0064         }

0065     }

0066 

0067     /*初始化机器人的位置坐标*/

0068     robotY=n;

0069     step=1;    /*机器人走出第一步*/

0070     map[robotX][robotY]=step;

0071 

0072     /*机器人根据行动策略表,决定自己的下一步行动方向*/

0073     step=step+1;

0074     while (step<=n*n) {

0075         /*只能向上,则向上*/

0076         if (map[robotX-1][robotY]==BLANK &&

0077             map[robotX+1][robotY]!=BLANK &&

0078             map[robotX][robotY-1]!=BLANK &&

0079             map[robotX][robotY+1]!=BLANK) {

0080             robotX=robotX-1;

0081         }

0082         else

0083             /*只能向下,则向下*/

0084             if (map[robotX-1][robotY]!=BLANK &&

0085                 map[robotX+1][robotY]==BLANK &&

0086                 map[robotX][robotY-1]!=BLANK &&

0087                 map[robotX][robotY+1]!=BLANK) {

0088                 robotX=robotX+1;

0089             }

0090             else

0091                 /*只能向左,则向左*/

0092                 if (map[robotX-1][robotY]!=BLANK &&

0093                     map[robotX+1][robotY]!=BLANK &&

0094                     map[robotX][robotY-1]==BLANK &&

0095                     map[robotX][robotY+1]!=BLANK) {

0096                     robotY=robotY-1;

0097                 }

0098                 else

0099                     /*只能向右,则向右*/

0100                     if (map[robotX-1][robotY]!=BLANK &&

0101                         map[robotX+1][robotY]!=BLANK &&

0102                         map[robotX][robotY-1]!=BLANK &&

0103                         map[robotX][robotY+1]==BLANK) {

0104                         robotY=robotY+1;

0105                     }

0106                     else

0107                         /*向左或向下,则向下*/

0108                         if (map[robotX+1][robotY]==BLANK &&    map[robotX][robotY-1]==BLANK) {

0109                             robotX=robotX+1;

0110                         }

0111                         else

0112                             /*向左或向上,则向左*/

0113                             if (map[robotX-1][robotY]==BLANK &&    map[robotX][robotY-1]==BLANK) {

0114                                 robotY=robotY-1;

0115                             }

0116                             else

0117                                 /*向右或向上,则向上*/

0118                                 if (map[robotX-1][robotY]==BLANK &&    map[robotX][robotY+1]==BLANK) {

0119                                     robotX=robotX-1;

0120                                 }

0121                                 else

0122                                     /*向右或向下,则向右*/

0123                                     if (map[robotX+1][robotY]==BLANK &&    map[robotX][robotY+1]==BLANK) {

0124                                         robotY=robotY+1;

0125                                     }

0126         map[robotX][robotY]=step;

0127         step=step+1;

0128     }

0129 

0130     /*打印机器人走出的地图*/

0131     for (i=1; i<=n; i=i+1) {

0132         for (j=1; j<=n; j=j+1) {

0133             printf("%3d ",map[i][j]);    /*每个输出数据占3个字符的宽度*/

0134         }

0135         printf("\n");    /*每行的结尾打印一个回车换行标记*/

0136     }

0137     return 0;

0138 }

你可能感兴趣的:(例题3-2,蛇形填数,解题报告)