[chess] 체스 판에서 기사의 최단 경로

나는 다가오는 프로그래밍 대회를 위해 연습을 해왔고 완전히 어리둥절한 질문을 우연히 발견했습니다. 하지만 결코 떠오르지 않는다는 손가락을 꼬집기보다는 지금 배워야 할 개념 인 것 같습니다.

기본적으로 체스 판에있는 기사를 다룹니다. 시작 위치와 끝 위치의 두 가지 입력이 제공됩니다. 목표는 기사가 목표 위치에 도달하기 위해 취할 수있는 최단 경로를 계산하고 인쇄하는 것입니다.

나는 최단 경로 같은 것을 다루지 않았고 어디서부터 시작해야할지조차 모른다. 이 문제를 해결하기 위해 어떤 논리를 사용합니까?

추신 : 만약 관련성이 있다면, 그들은 광장의 중심이 다음과 같을 때 기사가 만들 수있는 (잠재적으로) 8 번의 동작으로 형성된 사각형의 네 모서리로 이동할 수 있도록 허용하여 기사의 정상적인 동작을 보완하기를 원합니다. 기사의 위치.



답변

사용 가능한 모든 이동이 연결되고 (값 = 1) 사용할 수없는 이동이 연결 해제 된 (값 = 0) 그래프가 있습니다. 희소 행렬은 다음과 같습니다.

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

그래프에서 두 점의 최단 경로는 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. 바보처럼 http://en.wikipedia.org/wiki/A*_algorithm을 사용하는
    것이 더 빠를 수 있다고 말했습니다
    .
  2. 가장 빠른 방법은 모든 거리를 미리 계산하여 8×8 풀 매트릭스에 저장하는 것입니다. 음, 나는 그것을 속임수라고 부르고 문제가 작기 때문에 작동합니다. 그러나 때로는 대회가 프로그램 실행 속도를 확인합니다.
  3. 요점은 프로그래밍 대회를 준비하고 있다면 Dijkstra를 포함한 일반적인 알고리즘을 알아야한다는 것입니다. 좋은 시작점은 Introduction to AlgorithmsISBN 0-262-03384-4를 읽는 것
    입니다. 또는 wikipedia, http://en.wikipedia.org/wiki/List_of_algorithms를 사용해 볼 수 있습니다 .

답변

편집 : 여기에 제시된 공식을 수정 한 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 Day 1 에 요청되었으며
솔루션은 여기에 있습니다.


답변

여기에 올바른 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) 제약 조건을 충족합니다.


답변

최근에 만난 매우 흥미로운 문제입니다. 몇 가지 솔루션을 찾은 후 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);
}

이 공식은 (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.


답변

예, 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)에서 (x0, y0)까지의 최단 경로보다 정확히 2 번 더 S4. 그리고 후자의 문제는 간단한 반복으로 신속하게 해결할 수 있습니다.

N = max (| x1-x0 |, | y1-y0 |)라고합시다. N> = 4이면 (x0, y0)에서 (x1, y1)까지의 최단 경로에 ceil (N / 2) 단계가 있습니다.


답변

위의 O (1) 답변 [ https://stackoverflow.com/a/8778592/4288232 by Mustafa Serdar Şanlı]이 실제로 작동하지 않습니다. ((1,1) 또는 (3,2) 또는 (4,4)를 확인하고 (1,0) 또는 (2,2)의 명백한 가장자리 사례를 제외)).

다음은 훨씬 더 추악한 솔루션 (파이썬)으로 작동합니다 ( “테스트”가 추가됨).

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


답변

당신이해야 할 일은 기사의 가능한 움직임을 그래프로 생각하는 것인데, 보드의 모든 위치는 노드이고 가능한 이동은 다른 위치로 에지로 간주됩니다. 모든 모서리가 동일한 가중치 또는 거리를 갖기 때문에 dijkstra의 알고리즘이 필요하지 않습니다 (모두 수행하기 쉽고 짧습니다). 시작 지점에서 끝 위치에 도달 할 때까지 BFS 검색을 수행 할 수 있습니다.