チェス盤上の騎士の最短経路


95

私は今度のプログラミング競争のために練習しており、私はちょうど完全に当惑している質問に出くわしました。でも、それを思いついたのではなく、今から学ばなければならないコンセプトだと思います。

基本的に、それはチェス盤上の騎士の駒を扱います。開始位置と終了位置の2つの入力が与えられます。目標は、騎士が目標の場所に到達するために使用できる最短経路を計算して出力することです。

私は最短経路のようなものを扱ったことがなく、どこから始めればよいかもわかりません。これに取り組むためにどのようなロジックを採用しますか?

PS何らかの関連性がある場合、彼らはあなたがあなたがナイトの通常の動きを補足することを望んでいます騎士の場所。


PSを明確にしていただけませんか?つまり、騎士がE4にいる場合、C2、C6、G2、G6に移動できますか?
スティーブTjoa

はい、通常の動きに加えて。
カイル・ヒューズ

1
ここでは、問題のいくつかの数学的な分析です:math.stackexchange.com/questions/104700/...
グレアム・パイル

回答:


28

ここにグラフがあります。利用可能なすべての動きが接続され(値= 1)、利用できない動きは接続されていません(値= 0)。疎行列は次のようになります。

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

グラフの2つのポイントの最短経路は、http://en.wikipedia.org/wiki/Dijkstra's_algorithmを使用して見つけることができます

wikipedia-pageからの疑似コード:

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. moron氏として、http://en.wikipedia.org/wiki/A*_algorithmを使用 するとより高速になる可能性があると述べました 。
  2. 最速の方法は、すべての距離を事前に計算し、それを8x8の完全な行列に保存することです。まあ、私はそれを不正行為と呼び、問題が小さいためにのみ機能します。しかし、競技会がプログラムの実行速度をチェックすることもあります。
  3. 重要な点は、プログラミングの競争に備える場合は、ダイクストラを含む一般的なアルゴリズムを知っている必要があるということです。Introduction to AlgorithmsISBN 0-262-03384-4を読むことから始めるのが良い でしょう。または、ウィキペディアを試すこともできます。http://en.wikipedia.org/wiki/List_of_algorithms

これは、以下のムスタファの解決策と比較して複雑なようです。
lpapp 14

使用できない移動とはどういう意味ですか?騎士はどんな広場にも到達できます!?
everlasto

51

編集: 彼がここに提示された式を修正したサイモンの答えを参照してください

実際には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 Day 1で尋ねられました。
解決策はこちらです


8
その式をどのように作成したか説明できますか?
kybernetikos 2013

3
このコードは機能しますか?騎士が(0,0)の位置で、ポイント(1,0)に移動したい場合。これは0 <= y <= xを満たします。デルタ= 1-0 =1。yはデルタよりも大きくありません(0 <1)。つまり、私はelseケースに行くつもりです。デルタ-2 *((デルタ-y)/ 4)= 1-2((1-0)/ 4)= 1-1 / 2 = 1。騎士を(0,0)から(1,0)に一度に移動できる理由はありません。問題は、このアルゴリズムが機能するかどうかです。または私は何を間違っていますか?
SimpleApp

3
直接可能なポジションでのみ機能するようです。しかし、ユーザーが(2,2)を指定すると0を返し、ユーザーが(4,4)を指定すると2を返しますが、これは誤りです。
yunas

6
である必要が2 * floor((y - delta) / 3) + deltaありdelta - 2 * floor((delta - y) / 4)ます。これはこのコンテストページの公式ソリューションですが、間違っています。この最初の方程式(から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

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回の移動で最も近い「2」のスペース(b2)に到達できません。そのため、最初の移動は(a3)の左側の存在しないスペースでなければなりません。
John Hascall 2016年

そうです、私は無限のチェス盤の仮定を含むように私の答えを変更しました
Graeme Pyle 2016年

@JonatasWalker説明してください。(8,0)から(0,0)への問題はないと思います。4手?
Graeme Pyle 2016年

申し訳ありませんが、@ GraemePyle、私のせいで、コメントを削除しています。
Jonatas Walker 2016

2
こんにちは@GraemePyle-私はあなたに同意します。これは全体的なプログラミングの最善のアプローチです。ところで素晴らしい図!
Fattie、2016年

22

私が最近遭遇した非常に興味深い問題。いくつかのソリューションを探した後O(1) time and space complexitySACO 2007 Day 1 ソリューションで与えられた分析式()を回復するように試みられました。

まず最初に、数式を修正するのに役立つ非常に優れた視覚化について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);
}

この式は、(1,0)および(2,2)のコーナーケースを除くすべての(x、y)ペア(軸と対角対称を適用した後)で機能します。

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)位置のステップ数を計算できます。基本的に、この式ではボードに依存しないため(「無限ボード」とは、そのプロパティを意味します)、したがって機能します。
サイモン2017年

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 2017

1
念のため、BAPC2017コンテストでは、無限のボード上でKnight's Marathonという名前の質問もありました。2017.bapc.eu/files/preliminaries_problems.pdf
Amir-Mousavi

19

はい、ダイクストラと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)までの最短経路は、正確に2つの移動を必要とします。

セットS3(同様に定義)の24点のいずれかから(x、y)への最短経路には、少なくとも2つの移動が必要です。

したがって、| x1-x0 |> 4または| y1-y0 |> 4の場合、(x0、y0)から(x1、y1)への最短経路は、(x0、y0)から最短経路よりも2移動だけ大きくなります。 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)で正方形を描く。このセット(call S4)には32ポイントが含まれます。」それはそうではありません、それはそれが9x9の正方形なので81を含みますか?また、「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

1
(1)セットは、正方形自体の境界上の点のみを参照します。それは32ポイント/場所を持っています。(2)補足的な動きに関する投稿者のPS(元の投稿のコメントも参照)を考慮すると、最小の動き数は2になります。
スティーブTjoa

ありがとう、それは今では理にかなっています:)
satoshi

ボードが無限の場合はどうなりますか?この場合、BFSのみが適切に機能します
Oleg Abrazhaev

あなたは(+ -2、+ -2)の動きを述べ、なぜそれが騎士のために不可能だとして申し訳ありません@SteveTjoa、私は、理解できない
パベルBely

12

上記のO(1)回答[ https://stackoverflow.com/a/8778592/4288232 by Mustafa SerdarŞanlı]は実際には機能していません。((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
回答を参照するabovebelow、SOで実際に機能しない。
Petr Peller 2016

1
これがPython 2/3の私のバージョンです。解決機能を単純化しようとしましたが、簡単ではありません!gist.github.com/TimSC/8b9a80033f3a22a708a4b9741931c591
TimSC

9

あなたがしなければならないことは、騎士の可能な動きをグラフとして考えることです。ボード上のすべての位置がノードであり、他の位置への可能な動きがエッジです。すべてのエッジの重みまたは距離が同じであるため、ダイクストラのアルゴリズムは必要ありません(すべてのエッジは同じように簡単または短いため)。開始点から終了位置に到達するまで、BFS検索を実行できます。


1
+ !、この特定の問題にはBFSで十分です。
TiansHUo 2010

3
BFSで十分な場合もありますが、プレーンなBSTは多くのクエリで爆発します。訪問したマスをキャッシュする必要があります。そして、BFSはダイクストラのアルゴリズムのように見え始めます...
Charles Stewart

これまでに移動したすべての位置を追跡するための最良の方法は何ですか?BFSツリーは前方にのみ成長し、新しいポイントから利用可能なノードを発見したときに、古いノードを再び追加してしまうことはありません。無限ループ!
Nitish Upreti、2011

ここで、最後の騎士の位置を保存するだけで実行できると思いますか?
Nitish

7

Pythonの第一原理からのソリューション

私は最初にCodilityテストでこの問題に遭遇しました。彼らはそれを解決するために私に30分を与えました-この結果を得るのにそれよりもかなり長い時間がかかりました!問題は、正当な騎士の動きのみを使用して、騎士が0,0からx、yに移動するのに必要な動きの数です。xとyは多かれ少なかれ無制限でした(したがって、ここでは単純な8x8チェス盤については触れていません)。

彼らはO(1)ソリューションを望んでいました。私はプログラムが問題を明確に解決しているソリューションを求めていました(つまり、Graemeのパターンよりも明らかに正しいものを求めていました。ムスタファのソリューションのように、議論の余地のない式

だから、ここに私の解決策があります。他の人がそうであるように、解が軸と対角線に関して対称であることに注意して始めます。したがって、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の騎士は、一連の8時の動き(つまり、(-2、-1)の動き)を使用して、チートシートに戻ることができます。x、yが正中線より上にある場合、8時と7時の移動が連続する必要があり、正中線より下にある場合、8時と10時の連続が必要です。時計が動く。ここで注意すべき2つの点:

  • これらのシーケンスは、おそらく最短経路です。(それを証明してほしいですか、それとも明白ですか?)
  • そのような動きの数だけを気にします。動きを任意の順序で組み合わせることができます。

それでは、上記の正中線の動きを見てみましょう。私たちが主張しているのは、

  • (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)は原点の近くにあります(「近傍」が何であれ)。

これらの項を計算するコード内の2行は、これらのソリューションですが、いくつかの有用なプロパティを持つように選択されています。

  • 上記の正中式は、(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時だった場合(任意の順序で動きを作ることができるので、それは資格があります)、(3,1)を通過している必要があります。元のチートシートから参照してください)。したがって、私たちがすべきことは、1つの8時の移動をバックトラックし、合計2つの移動を節約することです。

右上にある4と同様の最適化があります。そこから始める以外に、そこに到達する唯一の方法は、(4,3)から8時の方向に移動することです。それはチートシートにはありませんが、そこにある場合、距離は3になります。代わりに、7を(3,1)に合わせることができ、距離が2しかないため、1をバックトラックする必要があります。 8時の位置に移動してから、7時の方向に1つ進みます。

したがって、チートシートにもう1つ番号を追加する必要があります。

. . 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のいずれかでなければなりません。すべてのm。(これの証明が必要ですか?)さらに、sが原点でない限り、距離がd-1の正方形が少なくとも1つ必要です。したがって、このプロパティがすべての正方形に適用されることを示すことにより、正確性を証明できます。したがって:

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")

または、下り坂から原点までのルートを追跡することにより、任意の1つの正方形の正しさを証明できます。最初に、上記のようにsの妥当性を確認し、次に距離(s + m)== d-1となるs + mを選択します。原点に到達するまで繰り返します。

ハウザット?


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で実装されたこの特定の問題の解決策です。最短経路の1つが表示されます-場合によっては複数あることもあります。

上記のアルゴリズムは使用しませんでしたが、他のソリューションと比較するとよいでしょう。

#!/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
重複を避けるためにさらに最適化できます。
アルン、2017年

-1

ムスタファセルダルシャンルコードに基づく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 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.