2009 英特尔® 线程挑战赛—骑士巡游
下载源码
问题描述
骑士巡游问题是指使用国际象棋棋盘上的一个马,尝试按照马的标准走法(向右或向左移动两格,然后再向上或向下移动一格;或者向上或向下移动两格,然后再向右或向左移动一格)走过棋盘上的每一格且每格只能走一次。对于爱好旅行的骑士,游遍整个棋盘是一件很有成就感的事情。但在经济困难的时候,可能无法走遍棋盘的每个方格。因此,有闲暇的骑士就想知道从给定的起点方格出发,然后再回到这个起始方格(封闭式路线)的旅程究竟有多少种走法。
问题:编写一段多线程代码,用来计算马在棋盘上从给定方格出发,走过一定格数可能采用的封闭走法总数。要使用的输入文件和输出文件的名称将通过命令行指定。输入文件应详细列出要走的棋盘大小和形状、马的起点方格、要走的步数以及要在输出文件中完整打印的走法数量。执行应用程序后,输出文件应包含所要求数量的走法(要走过的棋盘方格的列表)以及一个汇总行,其中包含根据输入文件中给定的条件而得出的可能走法总数。
输入文件格式:输入文件应包含五行,格式如下所示:
第 1 行 – 一个整数,指定棋盘上的列数(纵列数)
第 2 行 – 一个整数,指定棋盘上的行数(横排数)
第 3 行 – 以代数记谱法(用一个小写字母指定列的位置,一个整数指定行的位置)表示的马的起点方格
第 4 行 – 一个整数,指定要走过的方格数
第 5 行 – 一个整数,指定要在输出文件中打印的完整走法数
输出文件格式:每个打印的完整走法应该是一列以代数记谱法表示的棋盘方格,每个方格占一行,从输入文件中给定的方格开始并以该方格结束。如果要走的步数指定为 8,那么打印的走法将在 9 行中列出 9 个方格(1 个起点方格和 8 个目标方格,其中最后一个目标方格就是起点方格)。在每种打印的走法后面,应打印一个空白行或其他格式的分隔行。打印完所要求数目的走法之后,应打印一个汇总行,给出合格走法的总数。
如果无法完成符合给定要求的走法,输出文件中应简单说明这种情况。
输入文件示例:
-------------------
8
8
a8
8
2
输出文件示例:
-------------------
a8
b6
d7
f6
h7
f8
e6
c7
a8
-------
a8
b6
d5
c3
a4
c5
e6
c7
a8
-------
总共有 5634 种可能的 8 步走法
计时:将使用“骑士巡游”应用程序的总执行时间进行计分。(要获得最准确的计时结果,提交的代码应包含用于计时并将时间打印到标准输出的计时代码,否则将使用外部秒表进行计时。)
串行算法
骑士巡游的常规解法为递归和回溯,先将棋盘的每个格子均置为0,表示未到达过,根据骑士的行走规则,如果当前骑士在格子[x,y]中,骑士的目标格子为[x-2,y-1],[x-2, y-1] , [x-1, y-2],[x-1, y+2],[x+1, y-2],[x+1, y+2],[x+2, y-1],[x+2, y+1]这八个格子中,
骑士每移动到一个格子,将该格子置为1,如果八个格子均为1,表示巡游失败,回溯到上一个格子尝试其它方向。
由于每次移动骑士所在位置的x坐标+y坐标的和奇偶性将发生改变,骑士由起点出发再回到起点,必然是通过偶数次移动才可完成。
假设骑士的巡游路线为C1 C2 C3 C4……Cn C1(n为偶数),那么取路线的中点为C(n+1)/2,存在两条由C1到C(n+1)/2的路线,这两条路线没有交集即可,所以可以先通过递归和回溯的方法,取得由C1出发通过n/2次移动形成的所有路径,根据这些路径的终点进行分组,然后对每个分组内的路径两两比较,如果没有公共的格子则表示存在两条巡游路径C1 C2 C3 C4……Cn C1和C1 Cn Cn-1……C3 C2 C1。通过该算法可大大降低搜索复杂度。
并行算法
对于M*N的棋盘,最多存在(M*N)- 1个分组,分组内巡游路径的检测是相互独立的,可以完全并行的执行,通过OpenMP的parallel for即可将算法并行化。
串行代码:
uint64 XCheckPath(XPathArray* pPathArray, int nArray, int nStep)
{
uint64 nPathCnt = 0;
PUINT8 pMask =(PUINT8)scalable_calloc(sizeof(pMask[0]), nArray);
for(int i = 0; i < nArray ; ++i)
{