C,是一种信仰

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

作者fzd19zx@gmail.com 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 }
posted @ 2011-02-01 21:16  胖乎乎的王老师  Views(1203)  Comments(0Edit  收藏  举报
Email 联系我:fzd19zx@qq.com