棋Che上的骑士最短路径


95

我一直在为即将到来的编程比赛做练习,但偶然发现了一个我完全困惑的问题。但是,我觉得这是我现在应该学习的概念,而不是费力地讲它永远不会出现。

基本上,它处理棋盘上的骑士棋子。系统将为您提供两个输入:开始位置和结束位置。然后,目标是计算并打印骑士可以到达目标位置的最短路径。

我从来没有处理过最短路径的事情,甚至都不知道从哪里开始。我采用什么逻辑来解决这个问题?

PS:如果有任何相关性,他们希望您通过允许骑士移动到由骑士可以(可能)进行的(可能)八次移动所形成的正方形的四个角来补充骑士的常规移动,以补充正方形的中心骑士的位置。


您能澄清一下PS吗?您的意思是,如果骑士在E4上,可以移动到C2,C6,G2和G6?
史蒂夫·特约阿

是的,除了正常动作外。
凯尔·休斯

1
以下是对该问题的一些数学分析:math.stackexchange.com/questions/104700/…–
Graeme Pyle,

Answers:


28

您在这里有一个图,其中所有可用的移动都已连接(值= 1),而断开了不可用的移动(值= 0),则稀疏矩阵如下所示:

(a1,b3)=1,
(a1,c2)=1,
  .....

可以使用http://en.wikipedia.org/wiki/Dijkstra's_algorithm找到图中两点的最短路径

维基百科页面上的伪代码:

function Dijkstra(Graph, source):
   for each vertex v in Graph:           // Initializations
       dist[v] := infinity               // Unknown distance function from source to v
       previous[v] := undefined          // Previous node in optimal path from source
   dist[source] := 0                     // Distance from source to source
   Q := the set of all nodes in Graph
   // All nodes in the graph are unoptimized - thus are in Q
   while Q is not empty:                 // The main loop
       u := vertex in Q with smallest dist[]
       if dist[u] = infinity:
          break                         // all remaining vertices are inaccessible from source
       remove u from Q
       for each neighbor v of u:         // where v has not yet been removed from Q.
           alt := dist[u] + dist_between(u, v) 
           if alt < dist[v]:             // Relax (u,v,a)
               dist[v] := alt
               previous[v] := u
   return dist[]

编辑:

  1. 如白痴所说,使用 http://en.wikipedia.org/wiki/A*_algorithm 可以更快。
  2. 最快的方法是预先计算所有距离并将其保存到8x8全矩阵。好吧,我称其为作弊,并且仅因为问题很小而起作用。但是有时候比赛会检查您的程序运行的速度。
  3. 要点是,如果您准备参加编程竞赛,则必须了解包括Dijkstra算法在内的常见算法。阅读Introduction to AlgorithmsISBN 0-262-03384-4 是一个很好的起点 。或者您可以尝试维基百科,http://en.wikipedia.org/wiki/List_of_algorithms

与下面的Mustafa解决方案相比,这似乎很复杂。
lpapp 2014年

您无法采取行动是什么意思?骑士可以直达任何广场!!
everlasto

51

编辑: 参见simon的答案,他在那里固定了此处给出的公式。

其实有一个O(1)公式

这是我为使其形象化而制作的图像(骑士在 N 移动时可以到达的方块涂有相同的颜色)。 骑士的举动

你能注意到这里的模式吗?

尽管我们可以看到模式,但是很难找到f( x , y )返回从平方( 0 , 0 )到平方的所需移动数的函数( x , y )

但是这是当 0 <= y <= x

int f( int x , int y )
{
    int delta = x - y;

    if( y > delta )
        return 2 * ( ( y - delta ) / 3 ) + delta;
    else
        return delta - 2 * ( ( delta - y ) / 4 );
}

注意:此问题是在SACO 2007第1天提出的,
解决方案在这里


8
您是否有机会描述如何计算出该公式?
kybernetikos

3
此代码有效吗?如果骑士位于(0,0)的位置a,而我想将其移至(1,0)。满足0 <= y <= x。delta = 1-0 =1。y不大于delta(0 <1)。这意味着我要处理其他情况。delta-2 *((delta-y)/ 4)= 1-2((1-0)/ 4)= 1-1 / 2 = 1。我可以一口气将骑士从(0,0)移至(1,0)。问题是该算法有效吗?还是我做错了什么?
SimpleApp 2015年

3
似乎它仅适用于直接可能的职位。但是,如果用户提供(2,2),则返回0,如果用户提供(4,4),则返回2,这是错误的。
尤纳斯

6
应该是2 * floor((y - delta) / 3) + deltadelta - 2 * floor((delta - y) / 4)。这是该比赛页面上的官方解决方案,但这是错误的。这个第一个方程式(from if)返回错误的答案。在棋盘[-1000..1000] x [-1000..1000](2001x2001大)上(但逻辑上是无限的),给定的答案计算出4,004,001个字段中的2,669,329个正确(66.66%)。有人知道工作解决方案没有任何循环吗?
Robo Robok

2
我同意此解决方案无效。有关可用的O(1)解决方案,请参见其他答案,例如stackoverflow.com/a/26888893/4288232
TimSC 2013年

45

这是一个正确的O(1)解决方案,但对于骑士仅像国际象棋骑士那样在无限的国际象棋棋盘上移动的情况:

https://jsfiddle.net/graemian/5qgvr1ba/11/

找到这一点的关键是注意绘制电路板时出现的图案。在下图中,方格中的数字是到达该方格所需的最小移动量(您可以使用广度优先搜索来找到它):

模式

因为解在轴​​和对角线上是对称的,所以我只画了x> = 0和y> = x的情况。

左下方的块是起始位置,块中的数字表示到达这些块的最小移动量。

有3种模式需要注意:

  • 蓝色垂直组递增4
  • “主要”红色对角线(它们从左上到右下,像反斜杠一样)
  • “辅助”绿色对角线(与红色方向相同)

(请确保您看到的两组对角线都是从左上角到右下角。它们具有恒定的移动计数。左下角的右上角对角线要复杂得多。)

您可以为每个公式导出公式。黄色块是特殊情况。因此,解决方案变为:

function getMoveCountO1(x, y) {

    var newXY = simplifyBySymmetry(x, y);

    x = newXY.x;
    y = newXY.y;

    var specialMoveCount = getSpecialCaseMoveCount(x ,y);

    if (specialMoveCount !== undefined)
        return specialMoveCount;

    else if (isVerticalCase(x, y))
        return getVerticalCaseMoveCount(x ,y);

    else if (isPrimaryDiagonalCase(x, y))
        return getPrimaryDiagonalCaseMoveCount(x ,y);

    else if (isSecondaryDiagonalCase(x, y))
        return getSecondaryDiagonalCaseMoveCount(x ,y);

}

最难的是垂直群体:

function isVerticalCase(x, y) {

    return y >= 2 * x;

}

function getVerticalCaseMoveCount(x, y) {

    var normalizedHeight = getNormalizedHeightForVerticalGroupCase(x, y);

    var groupIndex = Math.floor( normalizedHeight / 4);

    var groupStartMoveCount = groupIndex * 2 + x;

    return groupStartMoveCount + getIndexInVerticalGroup(x, y);

}

function getIndexInVerticalGroup(x, y) {

    return getNormalizedHeightForVerticalGroupCase(x, y) % 4;

}

function getYOffsetForVerticalGroupCase(x) {

    return x * 2;

}

function getNormalizedHeightForVerticalGroupCase(x, y) {

    return y - getYOffsetForVerticalGroupCase(x);

}

其他情况请参见小提琴。

也许我错过了更简单或更优雅的图案?如果是这样,我很乐意见到他们。特别是,我注意到在蓝色垂直情况下有一些对角线图案,但我没有对其进行探索。无论如何,此解决方案仍满足O(1)约束。


这似乎无法处理(文字上的)极端情况。如果“ 0”是板子(a1)的左下角正方形,那么您不能分两次移动到最近的“ 2”空间(b2)。因为这样做,您的第一步是到(a3)左边的空白处。
John Hascall

是的,我更改了答案,以包括无限的国际象棋棋盘假设
Graeme Pyle,2016年

@JonatasWalker请解释,我看不到从(8,0)到(0,0)的问题。需要四步?
Graeme Pyle

抱歉@GraemePyle,我的错,删除我的评论。
Jonatas Walker

2
嗨,@ GraemePyle-我同意你的看法,这是最好的整体编程方法。顺便说一句大图!
Fattie

22

最近遇到的一个非常有趣的问题。查找了一些解决方案后,我试图恢复SACO 2007 Day 1 解决方案中O(1) time and space complexity给出的解析公式()。

首先,我要感谢Graeme Pyle的出色可视化功能,这有助于我修正公式。

由于某种原因(可能是出于简化或美观或只是一个错误),他们将minus符号移入了floor运算符,结果得出了错误的公式floor(-a) != -floor(a) for any a

这是正确的解析公式:

var delta = x-y;
if (y > delta) {
    return delta - 2*Math.floor((delta-y)/3);
} else {
    return delta - 2*Math.floor((delta-y)/4);
}

该公式适用于所有(x,y)对(在应用轴和对角线对称之后),除了(1,0)和(2,2)边角情况外,以下情况不满足模式和硬编码要求:

function distance(x,y){
     // axes symmetry 
     x = Math.abs(x);
     y = Math.abs(y);
     // diagonal symmetry 
     if (x < y) {
        t = x;x = y; y = t;
     }
     // 2 corner cases
     if(x==1 && y == 0){
        return 3;
     }
     if(x==2 && y == 2){
        return 4;
     }
    
    // main formula
    var delta = x-y;
		if(y>delta){
  		return delta - 2*Math.floor((delta-y)/3);
  	}
  	else{
  		return delta - 2*Math.floor((delta-y)/4);
  	}
}


$body = $("body");
var html = "";
for (var y = 20; y >= 0; y--){
	html += '<tr>';
	for (var x = 0; x <= 20; x++){
  	html += '<td style="width:20px; border: 1px solid #cecece" id="'+x+'_'+y+'">'+distance(x,y)+'</td>';
  }
  html += '</tr>';
}

html = '<table>'+html+'</table>';
$body.append(html);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

注意:jQuery仅用于说明,有关代码,请参见distance函数。


2
@OlegAbrazhaev“距离”函数是一种解析函数,可以计算O(1)时间中给定(x,y)位置的步数。基本上,在此公式中,我们不依赖董事会(我想用“无限董事会”指的是财产),因此它将起作用。
simon

2
@simon有人可以解释一下主要公式吗?我发现很难用简单的单词来解释它
MarBVI

1
@MarBVI如果我们靠近y = x线,则可以通过保持在y = x线附近来使x + y每移动3步。如果我们接近y = 0线,则可以通过保持在y = 0线附近而在每一步中将x减少2。这就是为什么我们有2种情况,更确切地说是我在接近特定行时所说的意思:1.在y = x行附近是指受y = x和y = x / 2行限制的部分(y> x / 2 )。2.在y = 0线附近,我指的是受y = 0和y = x / 2线(y <x / 2)限制的部分。综合以上所述,如果删除Math.floor并简化主公式,我们将得到以下公式:if(y> x / 2)then {return(x + y)/ 3} else {return x / 2}
simon

1
@simon太好了,这可以使您更清楚地了解自己的时间:)
MarBVI

1
为了以防万一,BAPC2017竞赛还在无限局面上有一个名为“骑士马拉松”的问题,该公式可以完美解决该问题。2017.bapc.eu/files/preliminaries_problems.pdf
阿米尔·穆萨维

19

是的,Dijkstra和BFS会为您提供答案,但我认为该问题的象棋上下文提供了可以比通用的最短路径算法更快的解决方案的知识,尤其是在无限的国际象棋棋盘上。

为简单起见,我们将棋盘描述为(x,y)平面。目标是仅使用候选步骤(+ -1,+-2),(+-2,+-1)和(+ -2)查找从(x0,y0)到(x1,y1)的最短路径,+-2),如问题的PS中所述

这是新的观察结果:绘制一个带有角(x-4,y-4),(x-4,y + 4),(x + 4,y-4),(x + 4,y + 4)的正方形。该集合(称为S4)包含32点。从这32个点中的任何一个到(x,y)的最短路径需要精确地进行两次移动

从集合S3(类似定义)的24个点中的任意一个点到(x,y)的最短路径至少需要两次移动

因此,如果| x1-x0 |> 4或| y1-y0 |> 4,那么从(x0,y0)到(x1,y1)的最短路径比从(x0,y0)到S4。后者的问题可以通过简单的迭代快速解决。

令N = max(| x1-x0 |,| y1-y0 |)。如果N> = 4,则从(x0,y0)到(x1,y1)的最短路径具有ceil(N / 2)步。


1
只是我对这个答案感到困惑吗?“绘制一个带有角(x-4,y-4),(x-4,y + 4),(x + 4,y-4),(x + 4,y + 4)的正方形。 S4)包含32点。不,不是的,它包含81,因为它是9x9的正方形?另外,“让N = max(| x1-x0 |,| y1-y0 |)。如果N> = 4,则从(x0,y0)到(x1,y1)的最短路径具有ceil(N / 2)脚步。” 这是不正确的,例如x0 = 0,y0 = 0,x1 = 4,y1 = 4,最短路径是4,而不是该公式建议的2。
satoshi 2015年

1
(1)集合仅指正方形自身边界上的点。那有32点/位置。(2)考虑到发帖人关于补充动作的PS(另请参阅原始帖子中的评论),最小动作数变为2。
史蒂夫·乔阿

谢谢,
这才

如果板子是无限的怎么办?在这种情况下,只有BFS可以正常工作
Oleg Abrazhaev

@SteveTjoa对不起,我不明白您为什么提到(+ -2,+ -2)举动,因为骑士不可能
Pavel Bely

12

上面的O(1)答案[ Mustafa SerdarŞanlı的https://stackoverflow.com/a/8778592/4288232 ]并没有真正起作用。(检查(1,1)或(3,2)或(4,4),除了(1,0)或(2,2)的明显边缘情况)。

下面是一个更丑陋的解决方案(python),它确实有效(添加了“测试”):

def solve(x,y):
        x = abs(x)
        y = abs(y)
        if y > x:
            temp=y
            y=x
            x=temp  
        if (x==2 and y==2):
            return 4
        if (x==1 and y==0):
            return 3

    if(y == 0 or float(y) / float(x) <= 0.5):
        xClass = x % 4
        if (xClass == 0):
            initX = x/2
        elif(xClass == 1):
            initX = 1 + (x/2)
        elif(xClass == 2):
            initX = 1 + (x/2)
        else:
            initX = 1 + ((x+1)/2)

        if (xClass > 1):
            return initX - (y%2)
        else:
            return initX + (y%2)
    else:
        diagonal = x - ((x-y)/2)
        if((x-y)%2 == 0):
            if (diagonal % 3 == 0):
                return (diagonal/3)*2
            if (diagonal % 3 == 1):
                return ((diagonal/3)*2)+2
            else:
                return ((diagonal/3)*2)+2
        else:
            return ((diagonal/3)*2)+1


def test():
    real=[
    [0,3,2,3,2,3,4,5,4,5,6,7,6,7],
    [3,2,1,2,3,4,3,4,5,6,5,6,7,8],
    [2,1,4,3,2,3,4,5,4,5,6,7,6,7],
    [3,2,3,2,3,4,3,4,5,6,5,6,7,8],
    [2,3,2,3,4,3,4,5,4,5,6,7,6,7],
    [3,4,3,4,3,4,5,4,5,6,5,6,7,8],
    [4,3,4,3,4,5,4,5,6,5,6,7,6,7],
    [5,4,5,4,5,4,5,6,5,6,7,6,7,8],
    [4,5,4,5,4,5,6,5,6,7,6,7,8,7],
    [5,6,5,6,5,6,5,6,7,6,7,8,7,8],
    [6,5,6,5,6,5,6,7,6,7,8,7,8,9],
    [7,6,7,6,7,6,7,6,7,8,7,8,9,8]]

    for x in range(12):
        for y in range(12):
            res = solve(x,y)
            if res!= real[x][y]:
                print (x, y), "failed, and returned", res, "rather than", real[x][y]
            else:
               print (x, y), "worked. Cool!"

test()

10
将答案称为对SO above还是below不起作用。
Petr Peller

1
这是我在python 2/3中的版本。我试图简化求解功能,但这并不容易!gist.github.com/TimSC/8b9a80033f3a22a708a4b9741931c591
TimSC 2013年

9

您需要做的是将骑士的可能移动视为一张图,图中板上的每个位置都是一个节点,并且可能移动到其他位置作为边缘。不需要dijkstra的算法,因为每个边缘都有相同的权重或距离(它们都一样容易或短促)。您可以从起点开始进行BFS搜索,直到到达终点为止。


1
+ !,对于此特定问题,BFS就足够了。
TiansHUo

3
BFS可能就足够了,但是普通的BST会耗用很多查询-您将需要缓存访问过的正方形。然后BFS开始看起来有点像Dijkstra的算法...
Charles Stewart 2010年

跟踪我们已经走过的所有位置的最佳方法是什么,以便BFS树仅向前增长,并且当我们发现从新点开始可用的节点时,我们就不必再添加旧的节点了。无限循环!
Nitish Upreti

我在这里假设我们可以通过存储我们的最后一个骑士职位来做到这一点?
Nitish Upreti

7

Python首要原理的解决方案

我首先在Codility测试中遇到了此问题。他们给了我30分钟的时间来解决它-我花了比这更长的时间才能达到这个结果!问题是:仅使用合法的骑士动作,一个骑士从0,0到x,y需要花费多少步。x和y几乎是无界的(因此我们在这里不是在谈论简单的8x8棋盘)。

他们想要一个O(1)解决方案。我想要一个解决方案,使程序可以明显解决问题(即,我想要比Graeme的模式更明显的东西-模式具有打破您不看的地方的习惯),我真的不想不必依赖于毫无争议的公式,例如Mustafa的解决方案

因此,这是我的解决方案,物有所值。和其他人一样,首先注意解关于轴和对角线对称,所以我们只需要求解0> = y> = x。为了简化说明(和代码),我将扭转这个问题:骑士从x,y开始,目标是0,0。

假设我们将问题缩小到原点附近。在适当的时候,我们将了解“附近”的实际含义,但现在,让我们在备忘单上写下一些解决方案(起源于左下角):

2 1 4 3
3 2 1 2
0 3 2 3

因此,在网格上给出x,y的情况下,我们可以读取到原点的移动次数。

如果我们是从网格外部开始的,则必须重新回到网格。我们引入“中线”,即以y = x / 2表示的线。该行上x,y处的任何骑士都可以使用一系列八点钟的移动(即:(-2,-1)的移动)返回到备忘单。如果x,y位于中线上方,那么我们需要连续8点钟和7点钟移动;如果x,y位于中线下方,则我们需要连续8点钟和10点钟时钟移动。这里要注意两件事:

  • 这些序列被证明是最短的路径。(想要我证明它,还是显而易见?)
  • 我们只关心此类举动的数量。我们可以按任意顺序混合和匹配这些动作。

因此,让我们看一下中线以上的动作。我们声称的是:

  • (dx; dy)=(2,1; 1,2)(n8; n7)(矩阵表示法,不带数学排版-列向量(dx; dy)等于方阵乘以列向量(n8; n7)- 8点钟的走数和7点钟的走数),类似地;

  • (dx; dy)=(2,2; 1,-1)(n8; n10)

我声称dx,dy将大致等于(x,y),因此(x-dx,y-dy)将在原点附近(无论“附近”是什么)。

代码中用于计算这些术语的两行是解决这些问题的方法,但是选择它们时要具有一些有用的属性:

  • 中线上方的公式将(x,y)移至(0,0),(1,1)或(2,2)之一。
  • 中线以下公式将(x,y)移至(0,0),(1,0),(2,0)或(1,1)之一。

(您想要这些证明吗?)因此,骑士的距离将是n7,n8,n10和备忘单[x-dx,y-dy]的总和,而我们的备忘单减少为:

. . 4
. 2 .
0 3 2

现在,这还不是故事的结局。查看底行的3。我们达到这一目标的唯一方法是:

  • 我们从那里开始
  • 我们按8点和10点的顺序搬到了那里。但是,如果最后一步是8点钟(由于我们可以按任意顺序进行运动,所以该时间应为8点钟),那么我们必须经过(3,1),其距离实际上是2(您可以请参阅原始备忘单)。因此,我们应该做的是回溯8点钟的移动,总共节省了2个移动。

右上方的4有一个类似的优化。除了从那里开始,唯一的到达方法是从(4,3)移8点钟。不在备忘单上,但是如果在那儿,它的距离将是3,因为我们可以将7点制到(3,1),而距离只有2。所以,我们应该回溯一个移动8点钟,然后向前移动7点钟。

因此,我们需要在备忘单上再加上一个数字:

. . 4
. 2 . 2
0 3 2

(注意:(0,1)和(0,2)有大量的回溯优化,但是由于求解器永远不会把我们带到那里,因此我们不必担心它们。)

因此,这里有一些Python代码可以对此进行评估:

def knightDistance (x, y):
    # normalise the coordinates
    x, y = abs(x), abs(y)
    if (x<y): x, y = y, x
    # now 0 <= y <= x

    # n8 means (-2,-1) (8 o'clock), n7 means (-1,-2) (7 o'clock), n10 means (-2,+1) (10 o'clock)
    if (x>2*y):
        # we're below the midline.  Using 8- & 10-o'clock moves
        n7, n8, n10 = 0,  (x + 2*y)//4,  (x - 2*y + 1)//4
    else:
        # we're above the midline.  Using 7- and 8-o'clock moves
        n7, n8, n10 = (2*y - x)//3, (2*x - y)//3,  0
    x -= 2*n8 + n7 + 2*n10
    y -= n8 + 2*n7 - n10
    # now 0<=x<=2, and y <= x.  Also (x,y) != (2,1)

    # Try to optimise the paths.
    if (x, y)==(1, 0): # hit the  3.  Did we need to?
        if (n8>0): # could have passed through the 2 at 3,1.  Back-up
            x, y = 3, 1; n8-=1;
    if (x, y)==(2, 2): # hit the 4.  Did we need to?
        if (n8>0): # could have passed through a 3 at 4,3.  Back-up, and take 7 o'clock to 2 at 3,1
            x, y = 3, 1; n8-=1; n7+=1

    # Almost there.  Now look up the final leg
    cheatsheet = [[0, 3, 2], [2, None, 2], [4]]
    return n7 + n8 + n10 + cheatsheet [y][x-y]

顺便说一句,如果您想知道一条实际路线,则此算法也提供了这一点:它只是n7个7点钟动作的连续序列,然后是(或散布着)n8个8点钟动作,n10 10-个动作凌晨时分,任何舞动都由备忘单决定(其本身可以在备忘单中)。

现在:如何证明这是正确的。仅将这些结果与正确答案的表格进行比较是不够的,因为问题本身是无限的。但是我们可以说,如果一个正方形s的骑士距离是d,那么如果{m}是s的合法移动集合,则(s + m)的骑士距离必须是d-1或d + 1对于所有米 (是否需要证明?)此外,必须至少存在一个距离为d-1的正方形,除非s为原点。因此,我们可以通过显示每个平方的该属性成立来证明正确性。从而:

def validate (n):

    def isSquareReasonable (x, y):
        d, downhills = knightDistance (x, y), 0
        moves = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1,  2)]
        for dx, dy in moves:
            dd = knightDistance (x+dx,  y+dy)
            if (dd == d+1): pass
            elif (dd== d-1): downhills += 1
            else: return False;
        return (downhills>0) or (d==0)

    for x in range (0,  n+1):
        for y in range (0,  n+1):
            if not isSquareReasonable (x,  y): raise RuntimeError ("Validation failed")

另外,我们可以通过追踪从下坡到原点的路线来证明任何一个平方的正确性。首先,如上所述检查s的合理性,然后选择任何s + m以使距离(s + m)== d-1。重复直到我们到达原点。

za?


2
/*
This program takes two sets of cordinates on a 8*8 chessboard, representing the
starting and ending points of a knight's path.
The problem is to print the cordinates that the knight traverses in between, following
the shortest path it can take.
Normally this program is to be implemented using the Djikstra's algorithm(using graphs)
but can also be implemented using the array method.
NOTE:Between 2 points there may be more than one shortest path. This program prints
only one of them.
*/

#include<stdio.h>

#include<stdlib.h>

#include<conio.h>

int m1=0,m2=0;

/*
This array contains three columns and 37 rows:
The rows signify the possible coordinate differences.
The columns 1 and 2 contains the possible permutations of the row and column difference 
between two positions on a chess board;
The column 3 contains the minimum number of steps involved in traversing the knight's 
path with the given permutation*/

int arr[37][3]={{0,0,0},{0,1,3},{0,2,2},{0,3,3},{0,4,2},{0,5,3},{0,6,4},{0,7,5},    {1,1,2},{1,2,1},{1,3,2},{1,4,3},{1,5,4},{1,6,3},{1,7,4},{2,2,4},{2,3,3},{2,4,2},
            {2,5,3},{2,6,3},{2,7,5},{3,3,2},{3,4,3},{3,5,4},{3,6,3},{3,7,4},{4,4,4},{4,5,3},{4,6,4},{4,7,5},{5,5,4},{5,6,5},{5,7,4},{6,6,5},{6,7,5},{7,7,6}};

void printMoves(int,int,int,int,int,int);
void futrLegalMove(int,int,int,int);
main()
{
  printf("KNIGHT'S SHORTEST PATH ON A 8*8 CHESSBOARD :\n");
  printf("------------------------------------------");
  printf("\nThe chessboard may be treated as a 8*8 array here i.e. the (1,1) ");
  printf("\non chessboard is to be referred as (0,0) here and same for (8,8) ");
  printf("\nwhich is to be referred as (7,7) and likewise.\n");
  int ix,iy,fx,fy;
  printf("\nEnter the initial position of the knight :\n");
  scanf("%d%d",&ix,&iy);
  printf("\nEnter the final position to be reached :\n");
  scanf("%d%d",&fx,&fy);
  int px=ix,py=iy;
  int temp;
  int tx,ty;
  printf("\nThe Knight's shortest path is given by :\n\n");
  printf("(%d, %d)",ix,iy);
  futrLegalMove(px,py,m1,m2);
  printMoves(px,py,fx,fy,m1,m2);
   getch();
} 

/*
  This method checkSteps() checks the minimum number of steps involved from current
  position(a & b) to final position(c & d) by looking up in the array arr[][].
*/

int checkSteps(int a,int b,int c,int d)
{  
    int xdiff, ydiff;
    int i, j;
    if(c>a)
        xdiff=c-a;
    else
        xdiff=a-c;
    if(d>b)
        ydiff=d-b;
    else
        ydiff=b-d;
    for(i=0;i<37;i++)
        {
            if(((xdiff==arr[i][0])&&(ydiff==arr[i][1])) || ((xdiff==arr[i][1])&& (ydiff==arr[i] [0])))
            {
                j=arr[i][2];break;
            }
        }

        return j;
}   

/*
This method printMoves() prints all the moves involved.
*/

void printMoves(int px,int py, int fx, int fy,int a,int b)
{    
 int temp;
 int tx,ty;
 int t1,t2;
  while(!((px==fx) && (py==fy)))
  {   
      printf(" --> ");
      temp=checkSteps(px+a,py+b,fx,fy);
      tx=px+a;
      ty=py+b;
      if(!(a==2 && b==1))
      {if((checkSteps(px+2,py+1,fx,fy)<temp) && checkMove(px+2,py+1))
      {temp=checkSteps(px+2,py+1,fx,fy);
       tx=px+2;ty=py+1;}}
      if(!(a==2 && b==-1))
      {if((checkSteps(px+2,py-1,fx,fy)<temp) && checkMove(px+2,py-1))
      {temp=checkSteps(px+2,py-1,fx,fy);
       tx=px+2;ty=py-1;}}
      if(!(a==-2 && b==1))
      {if((checkSteps(px-2,py+1,fx,fy)<temp) && checkMove(px-2,py+1))
      {temp=checkSteps(px-2,py+1,fx,fy);
       tx=px-2;ty=py+1;}}
      if(!(a==-2 && b==-1))
      {if((checkSteps(px-2,py-1,fx,fy)<temp) && checkMove(px-2,py-1))
      {temp=checkSteps(px-2,py-1,fx,fy);
       tx=px-2;ty=py-1;}}
      if(!(a==1 && b==2))
      {if((checkSteps(px+1,py+2,fx,fy)<temp) && checkMove(px+1,py+2))
      {temp=checkSteps(px+1,py+2,fx,fy);
       tx=px+1;ty=py+2;}}
      if(!(a==1 && b==-2))
      {if((checkSteps(px+1,py-2,fx,fy)<temp) && checkMove(px+1,py-2))
      {temp=checkSteps(px+1,py-2,fx,fy);
       tx=px+1;ty=py-2;}}
      if(!(a==-1 && b==2))
      {if((checkSteps(px-1,py+2,fx,fy)<temp) && checkMove(px-1,py+2))
      {temp=checkSteps(px-1,py+2,fx,fy);
       tx=px-1;ty=py+2;}}
      if(!(a==-1 && b==-2))
      {if((checkSteps(px-1,py-2,fx,fy)<temp) && checkMove(px-1,py-2))
      {temp=checkSteps(px-1,py-2,fx,fy);
       tx=px-1;ty=py-2;}}
       t1=tx-px;//the step taken in the current move in the x direction.
       t2=ty-py;//" " " " " " " " " " " " " " " " " " " " " y " " " " ".
       px=tx;
       py=ty;
       printf("(%d, %d)",px,py);
       futrLegalMove(px,py,t1,t2);
       a=m1;
       b=m2;
   }

} 

/*
The method checkMove() checks whether the move in consideration is beyond the scope of
board or not.
*/   

int checkMove(int a, int b)
{
    if(a>7 || b>7 || a<0 || b<0)
        return 0;
    else
        return 1;
}

/*Out of the 8 possible moves, this function futrLegalMove() sets the valid move by
  applying the following constraints
      1. The next move should not be beyond the scope of the board.
      2. The next move should not be the exact opposite of the previous move.
  The 1st constraint is checked by sending all possible moves to the checkMove() 
  method;
  The 2nd constraint is checked by passing as parameters(i.e. a and b) the steps of the 
  previous move and checking whether or not it is the exact opposite of the current move.
*/

void futrLegalMove(int px,int py,int a,int b)
{
     if(checkMove(px+2,py+1) && (a!=-2 && b!=-1))
         m1=2,m2=1;
     else
     {
         if(checkMove(px+2,py-1)&& (a!=-2 && b!=1))
             m1=2,m2=-1;
     else
     {
         if(checkMove(px-2,py+1)&& (a!=2 && b!=-1))
              m1=-2,m2=1;
     else
     {
         if(checkMove(px-2,py-1)&& (a!=2 && b!=1))
               m1=-2,m2=-1;
     else
     {
         if(checkMove(px+1,py+2)&& (b!=-2 && a!=-1))
               m2=2,m1=1;
     else
     {
         if(checkMove(px+1,py-2)&& (a!=-1 && b!=2))
               m2=-2,m1=1;
     else
     {
         if(checkMove(px-1,py+2)&& (a!=1 && b!=-2))
               m2=2,m1=-1;
     else
     {
         if(checkMove(px-1,py-2)&& (a!=1 && b!=2))
               m2=-2,m1=-1;
     }}}}}}}
}

//End of Program.

我还没有研究图形..关于通过简单数组实现它的问题,除此以外,我无法导出任何解决方案。我不把位置当作等级和文件(通常的国际象棋符号),而是当作数组索引。仅供参考,这仅适用于8 * 8的棋盘。始终欢迎任何改进建议。

*注释足以满足您对逻辑的理解。但是,您可能总是会问。

*已在DEV-C ++ 4.9.9.2编译器(Bloodshed Software)上检查。


2

我认为这也可能对您有所帮助。

NumWays(x,y)=1+min(NumWays(x+-2,y-+1),NumWays(x+-1,y+-2)); 

并使用动态编程来获取解决方案。

PS:它有点使用BFS,而不必麻烦声明图的节点和边缘。


1

这是在Perl中实现的针对此特定问题的解决方案。它会显示一条最短的路径-在某些情况下可能有不止一条。

我没有使用上述任何算法-但最好将其与其他解决方案进行比较。

#!/usr/local/bin/perl -w

use strict;

my $from = [0,0];
my $to   = [7,7];

my $f_from = flat($from);
my $f_to   = flat($to);

my $max_x = 7;
my $max_y = 7;
my @moves = ([-1,2],[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1]);
my %squares = ();
my $i = 0;
my $min = -1;

my @s = ( $from );

while ( @s ) {

   my @n = ();
   $i++;

   foreach my $s ( @s ) {
       unless ( $squares{ flat($s) } ) {
            my @m = moves( $s );
            push @n, @m;
            $squares{ flat($s) } = { i=>$i, n=>{ map {flat($_)=>1} @m }, };

            $min = $i if $squares{ flat($s) }->{n}->{$f_to};
       }
   }

   last if $min > -1;
   @s = @n;
}

show_path( $f_to, $min );

sub show_path {
    my ($s,$i) = @_;

    return if $s eq $f_from;

    print "$i => $f_to\n" if $i == $min;

    foreach my $k ( keys %squares ) {
       if ( $squares{$k}->{i} == $i && $squares{$k}->{n}->{$s} ) {
            $i--;
            print "$i => $k\n";
            show_path( $k, $i );
            last;
       }
    }
}

sub flat { "$_[0]->[0],$_[0]->[1]" }

sub moves {
    my $c = shift;
    my @s = ();

    foreach my $m ( @moves ) {
       my $x = $c->[0] + $m->[0];
       my $y = $c->[1] + $m->[1];

       if ( $x >= 0 && $x <=$max_x && $y >=0 && $y <=$max_y) {
           push @s, [$x, $y];
       }
    }
    return @s;
}

__END__

1
public class Horse {

    private int[][] board;
    private int[] xer = { 2, 1, -1, -2, -2, -1, 1, 2 };
    private int[] yer = { 1, 2, 2, 1, -1, -2, -2, -1 };
    private final static int A_BIG_NUMBER = 10000;
    private final static int UPPER_BOUND = 64;


    public Horse() {
        board =  new int[8][8];
    }

    private int solution(int x, int y, int destx, int desty, int move) {

        if(move == UPPER_BOUND) {
            /* lets put an upper bound to avoid stack overflow */
            return A_BIG_NUMBER;
        }

        if(x == 6 && y ==5) {
            board[6][5] = 1;
            return 1;
        }
        int min = A_BIG_NUMBER;
        for (int i = 0 ; i < xer.length; i++) {
            if (isMoveGood(x + xer[i], y + yer[i])) {
                if(board[x + xer[i]][y + yer[i]] != 0) {
                    min = Integer.min(min, 1 + board[x +xer[i]] [y +yer[i]]);                   
                } else {
                    min = Integer.min(min, 1 + solution(x + xer[i], y + yer[i], destx, desty, move + 1));   
                }                   
            }
        }   
        board[x][y] = min;
        return min;
    }


    private boolean isMoveGood(int x, int y) {
        if (x >= 0 && x < board.length && y >= 0 && y < board.length)
            return true;
        return false;
    }


    public static void main(String[] args) {

        int destX = 6;
        int destY = 7;
        final Horse h = new Horse();
        System.out.println(h.solution(0, 0, destX, destY, 0));
    }
}

0

只是从上面Graeme Pyle的答案的jsfiddle中获得的ruby代码,将所有多余的代码剥离,然后将剩下的代码转换为ruby只是为了通过他的算法获得解决方案,就好像在工作。仍在测试:

def getBoardOffset(board)
  return board.length / 2
end

def setMoveCount(x, y, count, board)
  offset = getBoardOffset(board)
  board[y + offset][x + offset] = count
end

def getMoveCount(x, y, board)
    offset = getBoardOffset(board)
    row = board[y + offset]
    return row[x + offset]
end

def isBottomOfVerticalCase(x, y)
    return (y - 2 * x) % 4 == 0
end

def isPrimaryDiagonalCase(x, y)
    return (x + y) % 2 == 0
end

def isSecondaryDiagonalCase(x, y)
    return (x + y) % 2 == 1
end

def simplifyBySymmetry(x, y)
    x = x.abs
    y = y.abs
    if (y < x)
      t = x
      x = y
      y = t
    end
    return {x: x, y: y}
end

def getPrimaryDiagonalCaseMoveCount(x, y)
    var diagonalOffset = y + x
    var diagonalIntersect = diagonalOffset / 2
    return ((diagonalIntersect + 2) / 3).floor * 2
end

def getSpecialCaseMoveCount(x, y)
    specials = [{
            x: 0,
            y: 0,
            d: 0
        },
        {
            x: 0,
            y: 1,
            d: 3
        },
        {
            x: 0,
            y: 2,
            d: 2
        },
        {
            x: 0,
            y: 3,
            d: 3
        },
        {
            x: 2,
            y: 2,
            d: 4
        },
        {
            x: 1,
            y: 1,
            d: 2
        },
        {
            x: 3,
            y: 3,
            d: 2
        }
    ];
    matchingSpecial=nil
    specials.each do |special|
      if (special[:x] == x && special[:y] == y)
        matchingSpecial = special
      end
    end
    if (matchingSpecial)
      return matchingSpecial[:d]
    end
end

def isVerticalCase(x, y)
  return y >= 2 * x
end

def getVerticalCaseMoveCount(x, y)
    normalizedHeight = getNormalizedHeightForVerticalGroupCase(x, y)
    groupIndex = (normalizedHeight/4).floor
    groupStartMoveCount = groupIndex * 2 + x
    return groupStartMoveCount + getIndexInVerticalGroup(x, y)
end

def getIndexInVerticalGroup(x, y)
    return getNormalizedHeightForVerticalGroupCase(x, y) % 4
end

def getYOffsetForVerticalGroupCase(x) 
    return x * 2
end

def getNormalizedHeightForVerticalGroupCase(x, y)
    return y - getYOffsetForVerticalGroupCase(x)
end

def getSecondaryDiagonalCaseMoveCount(x, y)
    diagonalOffset = y + x
    diagonalIntersect = diagonalOffset / 2 - 1
    return ((diagonalIntersect + 2) / 3).floor * 2 + 1
end

def getMoveCountO1(x, y)
    newXY = simplifyBySymmetry(x, y)
    x = newXY[:x]
    y = newXY[:y]
    specialMoveCount = getSpecialCaseMoveCount(x ,y)
    if (specialMoveCount != nil)
      return specialMoveCount
    elsif (isVerticalCase(x, y))
      return getVerticalCaseMoveCount(x ,y)
    elsif (isPrimaryDiagonalCase(x, y))
      return getPrimaryDiagonalCaseMoveCount(x ,y)
    elsif (isSecondaryDiagonalCase(x, y))
      return getSecondaryDiagonalCaseMoveCount(x ,y)
    end
end

def solution(x ,y)
  return getMoveCountO1(x, y)
end


puts solution(0,0)

唯一的目的是在任何人需要完整代码的情况下节省某人转换代码的时间。


0

这是Jules May函数的PHP版本

function knightDistance($x, $y)
{
    $x = abs($x);
    $y = abs($y);

    if($x < $y)
    {
        $tmp = $x;
        $x = $y;
        $y = $tmp;
    }

    if($x > 2 * $y)
    {
        $n7 = 0;
        $n8 = floor(($x + 2*$y) / 4);
        $n10 = floor(($x - 2*$y +1) / 4);
    }
    else
    {
        $n7 = floor((2*$y - $x) / 3);
        $n8 = floor((2*$x - $y) / 3);
        $n10 = 0;
    }

    $x -= 2 * $n8 + $n7 + 2 * $n10;
    $y -= $n8 + 2 * $n7 - $n10;

    if($x == 1 && $y == 0)
    {
        if($n8 > 0)
        {
            $x = 3;
            $y = 1;
            $n8--;
        }
    }
    if($x == 2 && $y == 2)
    {
        if($n8 > 0)
        {
            $x = 3;
            $y = 1;
            $n8--;
            $n7++;
        }
    }

    $cheatsheet = [[0, 3, 2], [2, 0, 2], [4]];

    return $n7 + $n8 + $n10 + $cheatsheet [$y][$x-$y];
}

0

这是我的程序。这不是一个完美的解决方案。递归函数中有很多更改。但是,最终结果是完美的。我尝试优化一下。

public class KnightKing2 {
    private static int tempCount = 0;

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        int ip1 = Integer.parseInt(in.nextLine().trim());
        int ip2 = Integer.parseInt(in.nextLine().trim());
        int ip3 = Integer.parseInt(in.nextLine().trim());
        int ip4 = Integer.parseInt(in.nextLine().trim());
        in.close();
        int output = getStepCount(ip1, ip2, ip3, ip4);
        System.out.println("Shortest Path :" + tempCount);

    }

    // 2 1 6 5 -> 4
    // 6 6 5 5 -> 2

    public static int getStepCount(int input1, int input2, int input3, int input4) {
        return recurse(0, input1, input2, input3, input4);

    }

    private static int recurse(int count, int tx, int ty, int kx, int ky) {

        if (isSolved(tx, ty, kx, ky)) {
            int ccount = count+1;
            System.out.println("COUNT: "+count+"--"+tx+","+ty+","+ccount);
            if((tempCount==0) || (ccount<=tempCount)){
                tempCount = ccount;
            }
            return ccount;
        }

            if ((tempCount==0 || count < tempCount) && ((tx < kx+2) && (ty < ky+2))) {
                if (!(tx + 2 > 8) && !(ty + 1 > 8)) {
                    rightTop(count, tx, ty, kx, ky);

                }
                if (!(tx + 2 > 8) && !(ty - 1 < 0)) {
                    rightBottom(count, tx, ty, kx, ky);
                }
                if (!(tx + 1 > 8) && !(ty + 2 > 8)) {
                    topRight(count, tx, ty, kx, ky);
                }
                if (!(tx - 1 < 0) && !(ty + 2 > 8)) {
                    topLeft(count, tx, ty, kx, ky);
                }
                if (!(tx + 1 > 8) && !(ty - 2 < 0)) {
                     bottomRight(count, tx, ty, kx, ky);
                }
                if (!(tx - 1 < 0) && !(ty - 2 < 0)) {
                     bottomLeft(count, tx, ty, kx, ky);
                }
                if (!(tx - 2 < 0) && !(ty + 1 > 8)) {
                    leftTop(count, tx, ty, kx, ky);
                }
                if (!(tx - 2 < 0) && !(ty - 1 < 0)) {
                    leftBottom(count, tx, ty, kx, ky);
                }
            }

        return count;

    }

    private static int rightTop(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 2, ty + 1, kx, ky);

    }

    private static int topRight(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 1, ty + 2, kx, ky);
    }

    private static int rightBottom(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 2, ty - 1, kx, ky);
    }

    private static int bottomRight(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 1, ty - 2, kx, ky);
    }

    private static int topLeft(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 1, ty + 2, kx, ky);
    }

    private static int bottomLeft(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 1, ty - 2, kx, ky);
    }

    private static int leftTop(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 2, ty + 1, kx, ky);
    }

    private static int leftBottom(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 2, ty - 1, kx, ky);
    }

    private static boolean isSolved(int tx, int ty, int kx, int ky) {
        boolean solved = false;
        if ((tx == kx) && (ty == ky)) {
            solved = true;
        } else if ((tx + 2 == kx) && (ty + 1 == ky)) { // right top
            solved = true;
        } else if ((tx + 2 == kx) && (ty - 1 == ky)) { // right bottom
            solved = true;
        } else if ((ty + 2 == ky) && (tx + 1 == kx)) {// top right
            solved = true;
        } else if ((ty + 2 == ky) && (tx - 1 == kx)) {// top left
            solved = true;
        } else if ((tx - 2 == kx) && (ty + 1 == ky)) { // left top
            solved = true;
        } else if ((tx - 2 == kx) && (ty - 1 == ky)) {// left bottom
            solved = true;
        } else if ((ty - 2 == ky) && (tx + 1 == kx)) { // bottom right
            solved = true;
        } else if ((ty - 2 == ky) && (tx - 1 == kx)) { // bottom left
            solved = true;
        }

        return solved;
    }

}

1
可以进一步优化以避免重复。
阿伦(Arun)

-1

这是基于Mustafa SerdarŞanlı代码的C版本,适用于finit板:

#include <stdio.h>
#include <math.h>

#define test(x1, y1, x2, y2) (sx == x1 && sy == y1 &&tx == x2 &&ty == y2) || (sx == x2 && sy == y2 && tx == x1 && ty==y1)

int distance(int sx, int sy, int tx, int ty) {
    int x, y, t;
    double delta;

    // special corner cases 
    if (test(1, 1, 2, 2) || 
        test(7, 7, 8, 8) || 
        test(7, 2, 8, 1) || 
        test(1, 8, 2, 7))
        return 4;

    // axes symmetry 
    x = abs(sx - tx);
    y = abs(sy - ty);

    // diagonal symmetry 
    if (x < y) {
        t = x;
        x = y;
        y = t;
    }

    // 2 corner cases
    if (x == 1 && y == 0)
        return 3;
    if (x == 2 && y == 2)
        return 4;

    // main
    delta = x - y;
    if (y > delta) {
        return (int)(delta - 2 * floor((delta - y) / 3));
    }
    else {
        return (int)(delta - 2 * floor((delta - y) / 4));
    }
}

在这里测试它与递归解决方案的证据


1
测试有限数量的案例并不能证明。
BlenderBender
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.