2011 英特尔® 线程挑战赛—Masyu Puzzle
源码下载
问题描述
Masyu Puzzle 是一款逻辑游戏,由日本的益智书籍和杂志出版商 Nikoli 开发。这款游戏的目标是在矩形网格上使用穿过单元格中心的水平或垂直线条绘制一个循环。该循环不可与自身相交、不可分叉,也不可两次经过同一个单元格。为了增加难度,某些单元格包含白色或黑色圆圈。该循环线必须经过所有带黑色或白色圆圈的单元格。此外,对于带圆圈的单元格,必须遵守以下有关循环线的条件:
经过白色圆圈的线条必须直接通过其单元格,并在白色圆圈旁边(左边或右边)的单元格中直角转弯;
经过黑色圆圈的线条必须在其单元格中直角转弯,然后直接通过两侧的下一个单元格,直至第二个单元格的中心。
问题描述: 编写一段多线程代码以找到 Masyu Puzzle 输入实例的解决方案。此应用程序的输入内容来自命令行上列出的第一个文本文件。此文件将包含游戏起始网格的初始配置。此应用程序的输出内容为环绕网格、穿过圆圈并返回到起始单元格的循环的路径。此路径将储存在命令行上列出的第二个文件中。
输入描述:此程序的输入内容来自命令行上一个指定的文本文件。该文件的第一行是表示网格大小(行数和列数)的两个整数。为设置一个共同的参考点,保持参考一致,将网格的左上角单元格设定为 (1 1) 位置。该文件的其余部分将包括两组行,用来描述白色和黑色圆圈在网格上的坐标。组中的第一行是“B”或“W”,指示后面行中是黑色还是白色圆圈的坐标。组中的下一行将是带圆圈的单元格的坐标。组中的每一行都将包含 10 个整数,彼此之间至少隔一个空格。 它们代表 5 个圆圈的坐标对。双零标记 (0 0) 表示圆圈坐标的终点。 每行 10 个整数的例外将发生在组的最后一行,其中可能少于 5 个坐标及标记。第一组的后面将是第二组,用于描述其他颜色圆圈的坐标。未列出的网格单元格为空(不带圆圈)。
输出描述:此应用程序将输出一个列表,其中列出循环线环绕网格并经过圆圈单元格的移动路径。第一行将是循环路径上任意单元格的坐标。从某个单元格的中心开始移动,到水平或垂直方向上相邻单元格的中心结束。每次移动都用一个对应移动方向的大写字母表示,“U”表示向上,“D”表示向下,“R”表示向右,“L”表示向左。输出文件中的行将包含 40 个字符,彼此之间无空格,并且最后一行可能会少于 40 个字符。如果跟踪,则最后的移动必须在输出文件的第一行所指定的单元格的中心结束。如果给定的方格图没有可能的解决方案,则输出文件应该包含一条消息,说明这一点。
输入命令行示例: ./masyu gridin.txt pathout.txt
输入文件示例,gridin.txt:
7 7
B
5 6 1 3 5 6 1 5 0 0
W
3 1 4 2 4 3 7 4 4 4
7 5 2 6 5 7 0 0
输出文件示例,pathout.txt:
1 7
DDDDDLDLLLULLUUUURDDDRUUUURRDDLDDRRUUUUR
计时:将使用此程序的总执行时间进行计分。为得到最准确的计时结果,所提交的代码需要包含计时代码并将计算出的总执行时间打印到标准输出,否则将使用外部秒表计时。
串行算法
将整个网格理解为房间,则每个房间有四扇门,而门有三个状态:待定,关闭,打开。迷题的解答即为,如何构造一条路径按规则通过所有含圆圈的房间。
1, 递归根据规则找出必须关闭的门
2, 关闭不可到达的房间门。
如果有关闭的门回到1。 得到如下结果:
3, 检测每个端点所在的房间的门,如果为待定状态,则向该门通向的房间移动,如果移动后无解,则关闭该门, 回到1。
4, 检测每个端点的连接数量必须为奇数
5, 检测所有端点是否可连通,如果不可以则无解
6, 选择最近的端点对,遍历可能的方向进行移动。
7, 检测某个房间如果进入后隔离出的两个连通区间,检测连通内的端点连接数量。
所以求解函数为递归结构
// 按指定方向移动端点后递归查找路径
int XEndPointMoveTry_Dir(XGrid* pCopy, XGrid* pGrid, int nTry,
XEndPoint* pEndPoint, int nIndex, int nDir)
{
XPath* pMovePath = NULL;
XEndPoint* pEndPointCur = NULL;
int nIndexCur,nDirCur;
XGridCopy(pCopy, pGrid);
pMovePath = &(*pCopy->pPathMap)[pCopy->ppCell[pEndPoint->y][pEndPoint->x]];
switch(nDir & 0x03)
{
case 0:
pMovePath->xEndPoint[nIndex].nDirOut = XDIR_U;
break;
case 1:
pMovePath->xEndPoint[nIndex].nDirOut = XDIR_D;
break;
case 2:
pMovePath->xEndPoint[nIndex].nDirOut = XDIR_R;
break;
case 3:
pMovePath->xEndPoint[nIndex].nDirOut = XDIR_L;
break;
}
// 移动端点
XEndPointMove(pCopy, pMovePath, nIndex);
// 关闭形成自连接的门
XGridCloseSelfLink(pCopy, pMovePath);
// 按规则移动端点
pEndPointCur = XGridMoveWithRule(pCopy, nTry, nIndexCur, nDirCur);
// 尝试移动下一端点
return XGridFindMoveTry(pCopy, nTry + 1, pEndPointCur, nIndexCur, nDirCur);
}
// 递归查找路径
int XGridFindMoveTry(XGrid* pGrid, int nTry, XEndPoint* pEndPoint, int nIndex, int nDir)
{
int nRet = 0;
if(pGrid->nPath == 0)
{
nRet = 1;
}
else if(pEndPoint != NULL)
{
XGrid* pCopy = XGridCreate(pGrid->nW, pGrid->nH);
int y = pEndPoint->y;
int x = pEndPoint->x;
uint8** ppDoorH = pGrid->ppDoorH;
uint8** ppDoorV = pGrid->ppDoorV;
for(int i = 0; i < 4 && nRet != 1; ++i, ++nDir)
{
if((nDir % 4) == 0 && ppDoorH[y][x] == XDOOR_OPEN)
{
nRet = XEndPointMoveTry_Dir(pCopy, pGrid, nTry, pEndPoint, nIndex, 0);
}
if((nDir % 4) == 1 && ppDoorH[y + 1][x] == XDOOR_OPEN)
{
nRet = XEndPointMoveTry_Dir(pCopy, pGrid, nTry, pEndPoint, nIndex, 1);
}
if((nDir % 4) == 2 && ppDoorV[y][x + 1] == XDOOR_OPEN)
{
nRet = XEndPointMoveTry_Dir(pCopy, pGrid, nTry, pEndPoint, nIndex, 2);
}
if((nDir % 4) == 3 && ppDoorV[y][x] == XDOOR_OPEN)
{
nRet = XEndPointMoveTry_Dir(pCopy, pGrid, nTry, pEndPoint, nIndex, 3);
}
}
if(nRet == 1)
{
XGridCopy(pGrid, pCopy);
}
else
{
nRet = 0;
}
XGridDestroy(&pCopy);
}
return nRet;
}
热点分析
使用Intel AmpLifier分析热点,结果如下:
分析结果显示热点处于
// 查找最佳端点及其移动方向
XEndPoint* XGridFindBestEndPointUseLen(XGrid* pGrid, int& nIndex, int& nDir)
{
XEndPoint* pEndPoint = NULL;
int nBegID = 0, nEndID = 0, nPathCnt = 1;
int j, nPath = 0, nLink = 0, nLastLink = 0,nLastIndexJ = 0;
XPath** pPath = pGrid->pPathTmp;
XPath *pMinI,*pMinJ;
int nIndexI,nIndexJ;
int nMin = XDISTANCE_INFINITY,nMinID = -1;
deque<XPath*> deqLink;
deque<int> deqBegin;
XGraph* pGraph = XGraphGet(pGrid->nH * pGrid->nW);
XGraphSetData(pGraph, pGrid);
map<int, XPath>::iterator iter;
for(iter = pGrid->pPathMap->begin(); iter != pGrid->pPathMap->end(); ++iter)
{
if(iter->second.nID >= 0)
{
if(deqLink.size() == 0) deqLink.push_back(&iter->second);
pPath[nPath++] = &iter->second;
deqBegin.push_back(iter->second.xEndPoint[0].y * pGrid->nW + iter->second.xEndPoint[0].x);
deqBegin.push_back(iter->second.xEndPoint[1].y * pGrid->nW + iter->second.xEndPoint[1].x);
}
}
// 求取端点间的最短路径
for(int i = 0; i < deqBegin.size(); ++i)
{
nBegID = deqBegin[i];
int* pDistance = pGraph->pDistance + nBegID * pGraph->nNode;
int* pPreNode = pGraph->pPreNode + nBegID * pGraph->nNode;
XDijkstra(pGraph, nBegID, pDistance, pPreNode);
}
。。。。。。
return pEndPoint;
}
并行算法
Cilk提供的cilk_for,可以并行for循环,而算法90%的时间集中在以下几行中
// 求取端点间的最短路径
for(int i = 0; i < deqBegin.size(); ++i)
{
nBegID = deqBegin[i];
int* pDistance = pGraph->pDistance + nBegID * pGraph->nNode;
int* pPreNode = pGraph->pPreNode + nBegID * pGraph->nNode;
XDijkstra(pGraph, nBegID, pDistance, pPreNode);
}
利用Cilk优化后代码如下:
// 求取端点间的最短路径
cilk_for(int i = 0; i < deqBegin.size(); ++i)
{
nBegID = deqBegin[i];
int* pDistance = pGraph->pDistance + nBegID * pGraph->nNode;
int* pPreNode = pGraph->pPreNode + nBegID * pGraph->nNode;
XDijkstra(pGraph, nBegID, pDistance, pPreNode);
}
重新编译后,再使用Amplifier检测Concurrency结果如下:
算法已经具有良好的并行度。使用Release版本在Intel(R) Core(TM) 2 Duo CPU T6570下测试。
输入数据如下:
16 33
B
2 14 2 31 3 4 3 10 3 21
3 27 4 8 4 25 5 3 5 10
5 20 5 27 6 11 6 28 7 7
7 24 8 8 8 25 9 13 9 30
10 3 10 20 11 9 11 26 12 7
12 24 13 10 13 27 14 8 14 25
0 0
W
1 6 1 23 2 10 2 27 3 3
3 5 3 20 3 22 4 2 4 9
4 13 4 19 4 26 4 30 5 6
5 23 6 10 6 15 6 27 6 32
7 8 7 11 7 25 7 28 8 2
8 5 8 9 8 13 8 15 8 19
8 22 8 26 8 30 8 32 9 4
9 10 9 12 9 21 9 27 9 29
10 4 10 7 10 21 10 24 11 1
11 5 11 15 11 18 11 22 11 32
12 3 12 12 12 20 12 29 13 14
13 31 14 9 14 10 14 26 14 27
15 5 15 22 0 0
输出结果:
1 6
RRRRDDRRRUURRRDLLDDLLLLLLUULDDDDDRRRUURR
RDLLDDLLLDDRURRRRUURRUURRRUUURRURDDLLDDD
LLDDLLLDDLLLDLLDDDDRUURRUURRRRURURURRDDL
LDDDRURRRRRUUULDDLLUUURRULLLUURRDRUULUUU
RRRRRDDRRRUURRRDLLDDLLLLLLUULDDDDDRRRUUR
RRDLLDDLLLDDRURRRRUURRUURDDDLLDDLLLDLLDD
DDRUURRUURRRRDLLDDLLLDDLLLUULLLDDLULLULL
UULLLDDDDLLLLLLUULLLDDLUULLUULUURRDDDRUR
RRUUULUULDDDRDLLUUULLLUUUUUUURRRDDLLDDDD
RUURRDRUULUUUR
串行版本:3.462231 seconds
并行版本:2.008358 seconds