mathe 发表于 2011-5-25 21:30:18

马踏棋盘回路计数问题

中国象棋(9×10)棋盘上一只马从任何一个位置出发,没有重复经过所有格子最后返回起始点的不同方案有多少种?
如果不需要返回起始点,那么又有多少种方案?

已添加到A193054, A193055

风云剑 发表于 2011-5-26 21:32:01

以前试过,相当多,时间太久

以下是转的
===========
/*
一、题目:

(马的遍历问题):设计程序完成如下要求:在中国象棋棋盘上,对任一位置上放置的一个马,均能选择一个合适的路线,使得该棋子能按象棋的规则不重复的走过棋盘上的每一个位置。

要求:(1)依次输出所走过的各位置的坐标。
(2)最好能画出棋盘的图形形式,并在其上动态的标注行走的过程。
(3)程序能方便的移植到其它规格的棋盘上。

二、设计思想概述:

根据设计的题目要求,首先先要了解中国象棋的棋盘规格以及马的走法。在中国象棋的棋盘上,和国际象棋一样也是8*8的方格,但是与国际象棋相比多出一条楚河汉界,而且按中国象棋中马的走法,马是在棋盘的点上行走的,而不是在格子上。因此在考虑实际问题时可以将点抽象成格子来处理,这样比较好理解和实现。 所以,中国象棋的棋盘可以抽象成10行9列的棋盘,马在格子上行走。
中国象棋中的马是走“日”字形的,也就是如果马在x轴方向移动两格,那么就在y轴方向上移动一格。类似的如果马在y轴方向移动两格,那么就在x轴方向上移动一格。

题目要求马在中国象棋棋盘上不重复的遍历。根据题意,设计了两种算法(以下分别称为方法一、方法二)

方法一:

马的遍历的过程也就是一个搜索的过程。在刚开始考虑问题的时候,我采用了深度优先搜索的办法,并且加上了回溯。但是在实际的问题实现的过程中却发现当棋盘规模比较小的时候,此方法很有效。可是当棋盘的规模增大时,长时间的运行却求不出结果。这时讨论原因,认为把搜索和回溯写成一个函数,在函数的反复调用的过程中耗费了大量的时间和空间。另外马从起始位置出发走第一步时候,最多有8种可能性,那么走第二步的时候有64种可能性,走第三步的时候就有了512种可能,依次类推可以知道要搜索的路径过多,根本不太可能在短时间内搜索出一条符合题意的路径,而且还要考虑回溯将同样耗费很多的时间。

综合以上的分析,对原先的算法加以改进。在搜索的过程中给程序加上一个启发条件可以有效的缩小搜索范围,并且加快程序的运行速度,另一方面将递归结构改写为非递归结构,去除递归调用的环节加快运行速度。

经过考虑,给搜索过程加上的启发条件为在走下一步棋的时候,考虑下一步棋子落点往各个方向走的可能性的数目,从中选出一个可能性较小的落点,下一步马就落在这个点上。
此算法具有良好的可移植性,可以方便的移植到不同规格的棋盘上。

方法二:

马的遍历过程也可以这样实现:将中国象棋棋盘上的每个点,抽象为图中的一个节点,只要找出包含这棋盘上所有点的哈密顿回路,那么不管马的起始位置在什么地方,都能不重复的走遍这个棋盘上的所有的点,也就是哈密顿回路中所有的点。
但是这个方法不具有良好的可移植性。因为对于找哈密顿回路是世界难题,国际上还没有一种可行的良好的方法。在本程序中的哈密顿回路是通过分割棋盘,然后拼凑而成的,其中经过了无数次的尝试。

三、 具体实现要点:

方法一:

设置一个二维数组a来代表马在棋盘上走的位置是马遍历的第几步,规定起始位置为第一步。

设置两个一维数组dx、dy来代表x轴和y轴方向的增量。

设置标志flag, 表示这个位置有没有被走过,如果未被走过则为-1,而走过则为0。

设置一维数组v为马走下一步可能落点走下一步的可能性。

设置二维数组d 保存v,方便回溯。

设置二维数组b保存回溯路线。

在搜索的时候,将原坐标加上各个增量,如果还在棋盘内,那么就走下一步。在这个基础上,再试着向各方向走,如果还在棋盘内,那么可能性就加1。比较这些点走下一步的可能性的大小,从中选择一个可能性最小的作为下一步的落点。然后重复先前的步骤,搜索路径。如果遇到无路可走的时候,就回溯,选择另一条路径搜索。如果搜索到一条遍历路径就输出,进行动态演示。如果没有,则显示“No answer!”

动态演示部分,用graphic.h头文件的函数line画线,函数circle画圆,函数itoa进行整型到字符型的转换,函数outtextxy进行字符输出显示。先画一个中国象棋的棋盘,然后将保存步数的数组a从1开始输出,每画一个圆,代表一个棋子,并在圆的圆心显示数。在要走下一步时候就按一下空格,显示下一步,直到走遍棋盘。


方法二:

“ma .ma”为存储10*9棋盘的哈密顿回路的文件,要注意的是哈密顿回路是以二维数组的形式存储的。

首先将文件读取到二维数组a中,找到马遍历的起点在回路中的位置,然后依次遍历哈密顿回路上的各个点,并将各个点为马所走的第几步的数据存储在另一个二维数组b中,最后将二维数组b输出,动态演示。

此方法的动态演示部分与方法一相同。

四、 测试结果

方法一的程序不能以如下点为起点进行遍历:

(x,y):x为横坐标,也就是棋盘的第几列;y为纵坐标,也就是棋盘的第几行

(2,2) (2,6) (3,4) (4,4) (6,6)
(6,8) (7,1) (8,2)

方法二的程序能以任意点为起点进行遍历。

*/

风云剑 发表于 2011-5-26 21:35:25

这是个示例,不针对楼主的问题
#include "stdio.h"
const int   nSize=6;                  //棋盘大小
int         s=1, x=0, y=0, x0=0, y0=0;//x,y为初始位置
int         htry1={ -2, -1, 1, 2, 2, 1, -1, -2 };
int         htry2={ 1, 2, 2, 1, -1, -2, -2, -1 };
int         board;

/* */
void print(void)
{   //打印一种跳法
    for(int i=0; i < nSize; i++)
    {
      for(int j=0; j < nSize; j++) printf("%4d", board);
      printf("\n");
    }
}

/* */
void try1(int nStep)
{   //为第nStep选择合适的位置
    for(int k=0; k < 8; k++)
    {
      int i=htry1;
      int j=htry2;
      if(x + i < nSize && x + i > -1 && y + j < nSize && y + j > -1 && !board)
      {
            board=nStep;
            x=x + i;
            y=y + j;
            if(nStep == nSize * nSize && (x - x0) * (x - x0) + (y - y0) * (y - y0) == 5)
            {
                printf("第%d种跳法是:\n", s++);
                print();
            }
            else
                try1(nStep + 1);
            board=0;
            x=x - i;
            y=y - j;
      }
    }
}

/* */
void main(void)
{
    for(int i=0; i < nSize; i++)
      for(int j=0; j < nSize; j++) board=0; //初始化棋盘
    board=1;
    try1(2);
}

风云剑 发表于 2011-5-26 21:42:38

还有这个
/*
马步遍历和骑士巡游问题。
马步遍历:在N*N棋盘上某一点开始以马步遍历整个棋盘,但不要求最后回到起点。
骑士巡游:在马步遍历的基础上要求最后回到起点。
*/
#include <stdio.h>
#include <time.h>

//#define _PRINT_FULL_RESULT_
#define _PRINT_SOME_RESULT_

#define MAX_SIZE    20
#define MAX_STEP    MAX_SIZE * MAX_SIZE
typedef struct
{
    long    x;
    long    y;
} STEP;
static long TryX[]={ -2, -1, 1, 2, 2, 1, -1, -2 };
static long TryY[]={ 1, 2, 2, 1, -1, -2, -2, -1 };
static char Board;//棋盘,比实际棋盘大4,因为有2格是边界。

//边界为值为-1,空白值为0,有棋子为1。
static char ResultBoard;    //用于输出结果的棋盘。
static STEP Step;                     //存储每一步的坐标。
static long CurStep;      //当前步,第一步是0。
static long Results;      //解的个数。
static long Hamilton;       //能够回到起点的解的个数。
static long SIZES, STEPS;   //实际的棋盘大小和步数。
FILE      *fp;            //用于记录结果的文件。

/* */
void Init(void) //棋盘初始化。
{
    for(long i=0; i < SIZES + 4; i++)
    {
      for(long j=0; j < SIZES + 4; j++)
      {
            Board=0;
      }
    }

    for(long i=0; i < SIZES + 4; i++)
    {
      Board= -1;
      Board= -1;
      Board= -1;
      Board= -1;
      Board= -1;
      Board= -1;
      Board= -1;
      Board= -1;
    }

    Results=0;
    Hamilton=0;
}

/* */
void Disp(void) //打印棋盘状态
{
    printf("\n-----------------------------------------\n");
    for(long i=0; i < SIZES + 4; i++)
    {
      for(long j=0; j < SIZES + 4; j++)
      {
            printf("%2d ", Board);
      }

      printf("\n");
    }
}

/* */
void Result(void)   //输出结果
{
    long    dx, dy;
    dx=Step.x - Step.x; //判断最后是否回到起点。
    dy=Step.y - Step.y;
    Results++;
#ifdef _PRINT_FULL_RESULT_
    printf("\n-------------- %ld --------------\n", Results);
    fprintf(fp, "\n-------------- %ld --------------\n", Results);
#endif
    if(dx * dx + dy * dy == 5)
    {
      Hamilton++;
#ifdef _PRINT_FULL_RESULT_
      printf("---------- Hamilton %d ----------\n", Hamilton);
      fprintf(fp, "---------- Hamilton %d ----------\n", Hamilton);
#endif
    }

#ifdef _PRINT_FULL_RESULT_
    for(long i=0; i < STEPS; i++)
    {
      ResultBoard.x - 2].y - 2]=i + 1;
    }

    for(long i=0; i < SIZES; i++)
    {
      for(long j=0; j < SIZES; j++)
      {
            printf("%4d", ResultBoard);
            fprintf(fp, "%4d", ResultBoard);
      }

      printf("\n");
      fprintf(fp, "\n");
    }

#elif defined _PRINT_SOME_RESULT_
    printf("\rResults: %ld, Hamiltons: %ld", Results, Hamilton);
#endif
}

/*
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -100N0N000 -1 -1
-1 -10N000N00 -1 -1
-1 -1000X0000 -1 -1
-1 -10N000N00 -1 -1
-1 -100N0N000 -1 -1
-1 -100000000 -1 -1
-1 -100000000 -1 -1
-1 -100000000 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
*/
void PutIt(void)
{
    long    NextX, NextY;
    for(long i=0; i < 8; i++)                   //8个方向上尝试,
    {
      NextX=Step.x + TryX;
      NextY=Step.y + TryY;
      if(Board == 0)            //如果这个位置是空,
      {
            Board=1;            //放置一个马,
            ++CurStep;
            Step.x=NextX;
            Step.y=NextY;            //计入步数,
            if(CurStep == STEPS - 1)            //如果步数达到最大步数(棋盘全满),
            {
                Result();                     //输出结果。
            }

            PutIt();                            //再做下一次尝试。
      }
    }

    Board.x].y]=0;//如果8个方向都不为空,将这次的马拿走,
    --CurStep;//并且步数倒退1步。
}

/* */
int main(void)
{
    long    SX, SY;
    time_tbegin_tm, end_tm;
#ifdef _PRINT_FULL_RESULT_
    fp=fopen("horse.txt", "w");
#endif
    printf("Board size ?(SIZE=?):");
    scanf("%ld", &SIZES);
    STEPS=SIZES * SIZES;
    printf("Start at ?(X=?):");
    scanf("%ld", &SX);
    printf("Start at ?(Y=?):");
    scanf("%ld", &SY);
    begin_tm=time(&begin_tm);
    Init();
    CurStep=0;
    Step.x=SX + 1; //第一步由输入产生。
    Step.y=SY + 1;
    Board.x].y]=1;//棋盘相应位置放置一个马。
#ifdef _PRINT_FULL_RESULT_
    fprintf(fp, " - Board size: %d*%d, start at (%d,%d)\n", SIZES, SIZES, SX, SY);
#endif
    PutIt();    //开始遍历。
    end_tm=time(&end_tm);
#ifdef _PRINT_FULL_RESULT_
    fprintf(fp, "\n-------------- END --------------\n");
    fprintf(fp, "Results: %ld, Hamiltons: %ld\n", Results, Hamilton);
    fprintf(fp, "-------------- %ld secs---------------\n", end_tm - begin_tm);
#endif
    printf("\n-------------- END --------------\n");
    printf("Results: %ld, Hamiltons: %ld\n", Results, Hamilton);
    printf("-------------- %ld secs---------------\n", end_tm - begin_tm);
    fclose(fp);
    return 0;
}

wayne 发表于 2011-5-27 08:58:59

mathe问的是计数问题,这个比风云剑的要弱很多,
我觉得这里面一定有可行的数学手段

KeyTo9_Fans 发表于 2011-5-27 14:48:10

此题与走格子路线数统计问题:

http://bbs.emath.ac.cn/viewthread.php?tid=517

是同一类问题。

在走格子路线数统计问题里,$4$种走法的位移为:

$(0,1)$、$(1,0)$、$(0,-1)$、$(-1,0)$

我们可以根据这$4$种走法,设计出$C(4,2)=6$种瓷砖:



于是路线数统计问题就变成了铺瓷砖方案数统计问题。

我们从上往下铺瓷砖。

铺的时候需要考虑:

1、新铺的瓷砖和已经铺好的瓷砖的边沿是否匹配。

2、新铺的瓷砖和已经铺好的瓷砖的连通性是否合法。

可以用动态规划算法统计合法的铺瓷砖方案总数。

由于所有走法的坐标变化的绝对值不超过$(1+0)$。

所以边沿状态只有一行格子的信息。

而马踏棋盘有$8$种走法:

$(1,2)$、$(2,1)$、$(1,-2)$、$(2,-1)$、$(-1,2)$、$(-2,1)$、$(-1,-2)$、$(-2,-1)$

一共有$C(8,2)=28$种瓷砖。

所有走法的坐标变化的绝对值不超过$(2+1)$。

所以边沿状态有$2$行加$1$个格子的信息。

即$19$个格子的信息。

信息包括:

1、该格子的两个端口(上一步从哪里跳过来,下一步跳到哪里去)已经确定了几个?用一个数字$0$、$1$、$2$表示。

2、对于标了数字$1$的格子,它们的连通情况。用$1a$、$1b$、$1c$……表示。相同的字母表示目前已经相连,不同的字母表示目前尚未相连。

暂不考虑第$2$条信息,在最坏的情况下第$1$条信息就已经有$3^19$种可能了,这个数有点大。但实际情况会好多少,这个暂时不知道。

xbtianlang 发表于 2011-5-28 22:11:20

马步遍历,不需要知道是白马还是黑马,只要标出每步的序号即可,比如:
   1223932   3244130   5
3835   2234031   42542
21903784335443   629
368734555069285326
8920856883565144   7
866788597449702752
19787382716057   845
668164775875481114
6318797261161346   9
806562177647101512
就是一个哈密顿回路。

xbtianlang 发表于 2011-5-30 17:40:43

在5×6的棋盘上找到8条哈密顿回路
path       1
   1282110   3
2211   22920
273023   4   9
1215   81924
   7261714   5
1613   62518
path       2
   1242110   3
2211   22920
253023   4   9
1215   81928
   7261714   5
1613   62718
path       3
   1282112   3
2211   22920
273023   413
1015   81924
   7261714   5
16   9   62518
path       4
   1242112   3
2211   22920
253023   413
1015   81928
   7261714   5
16   9   62718
path       5
   12413   6   3
14   5   22312
253011   4   7
1015261922
292017   827
16   9282118
path       6
   12213   6   3
14   5   22312
213011   4   7
1015261924
292017   827
16   9282518
path       7
   1241310   3
14   5   22312
253011   4   9
   615261922
292017   827
16   7282118
path       8
   1221310   3
14   5   22312
213011   4   9
   615261924
292017   827
16   7282518
这8条实质上可能是其中一、二条的几种对称状态。

风云剑 发表于 2011-5-30 21:37:02

1   8112029
102130   312
   7   2   92819
22172413   4
25   6151827
162326   514

   1   8112229
102130   312
   7   2   92823
20172413   4
25   6151827
161926   514


   1   4112029
102130   312
   5   2   92819
22172413   8
25   6151827
162326   714


   1   4112229
102130   312
   5   2   92823
20172413   8
25   6151827
161926   714


   110192229
182730   920
11   2212823
2617   613   8
   3121524   5
1625   4   714


   110192629
182730   920
11   2212825
2217   613   8
   3121524   5
1623   4   714


   1   8192229
182730   920
   7   2212823
2617   61310
   3121524   5
1625   41114


   1   8192629
182730   920
   7   2212825
2217   61310
   3121524   5
1623   41114

KeyTo9_Fans 发表于 2011-5-31 14:25:12

由于是回路,起点在哪里是无所谓的。

此外,每种方案都对应一种反向的方案。

我们不妨认为反向后的方案是同一种方案。

因此,对于$N*(N+1)$的棋盘,方案数如下:

$N=1$:?$0$
$N=2$:?$0$
$N=3$:?$0$
$N=4$:?$0$
$N=5$:?$8$
$N=6$:?$?$
$N=7$:?$?$
$N=8$:?$?$
$N=9$:?$?$

#####

$11#,19#,32#$提供的新数据:

$N=1$:?$0$
$N=2$:?$0$
$N=3$:?$0$
$N=4$:?$0$
$N=5$:?$8$
$N=6$:?$1067638$
$N=7$:?$34524432316$
$N=8$:?$7112881119092574$
$N=9$:?$?$
页: [1] 2 3 4 5 6 7
查看完整版本: 马踏棋盘回路计数问题