[Algorithm] Knight's Tour Algorithm

기사의 여행 알고리즘

Posted by lim.Chuck on October 12, 2024

Knight’s Tour

Knight’s Tour 문제는 체스판에서 체스의 나이트가 특정한 규칙에 따라 이동하며 모든 체스판의 칸을 정확히 한 번씩 방문하는 경로를 찾는 문제입니다. 나이트는 체스에서 L자 모양으로 이동하는 말로, 두 칸 이동한 후 직각으로 한 칸 이동하는 규칙에 따라 움직입니다.

문제의 목적

  • 목표는 나이트가 한 번씩만 체스판의 모든 칸을 방문할 수 있는 경로를 찾는 것입니다.
  • 이 문제는 백트래킹(Backtracking) 또는 Warnsdorff’s Rule과 같은 알고리즘을 사용하여 해결됩니다.

장점

  • 경로 탐색 및 최적화: 게임이나 경로 최적화 문제에서 활용할 수 있으며, 탐색 알고리즘 학습에 유용합니다.
  • 수학적 흥미: 그래프 이론 및 컴비네이터리얼 문제로 확장될 수 있어 수학적 접근 방식을 훈련할 수 있습니다.

단점

  • 복잡성: 큰 체스판에서는 해결하기 위한 계산량이 매우 커질 수 있습니다.
  • 백트래킹 의존: 여러 시도를 해야 하는 백트래킹 방식이 효율적이지 않을 수 있습니다.

JavaScript 예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const N = 8;
let board = Array.from({ length: N }, () => Array(N).fill(-1));

const movesX = [2, 1, -1, -2, -2, -1, 1, 2];
const movesY = [1, 2, 2, 1, -1, -2, -2, -1];

function isSafe(x, y, board) {
  return x >= 0 && x < N && y >= 0 && y < N && board[x][y] === -1;
}

function solveKnightTour(x, y, move, board) {
  if (move === N * N) return true;

  for (let i = 0; i < 8; i++) {
    const nextX = x + movesX[i];
    const nextY = y + movesY[i];

    if (isSafe(nextX, nextY, board)) {
      board[nextX][nextY] = move;
      if (solveKnightTour(nextX, nextY, move + 1, board)) {
        return true;
      } else {
        board[nextX][nextY] = -1; // 백트래킹
      }
    }
  }
  return false;
}

function knightTour() {
  board[0][0] = 0; // 시작 위치
  if (!solveKnightTour(0, 0, 1, board)) {
    console.log("해결할 수 없습니다.");
  } else {
    console.log(board);
  }
}

knightTour();

이 코드는 나이트의 투어를 백트래킹 알고리즘을 사용해 해결합니다. solveKnightTour 함수는 가능한 경로를 재귀적으로 탐색하고, 불가능하면 백트래킹을 수행합니다.


Rust 예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const N: usize = 8;
let mut board = [[-1; N]; N];

let moves_x: [isize; 8] = [2, 1, -1, -2, -2, -1, 1, 2];
let moves_y: [isize; 8] = [1, 2, 2, 1, -1, -2, -2, -1];

fn is_safe(x: isize, y: isize, board: &[[i32; N]; N]) -> bool {
    x >= 0 && x < N as isize && y >= 0 && y < N as isize && board[x as usize][y as usize] == -1
}

fn solve_knight_tour(x: isize, y: isize, move_num: i32, board: &mut [[i32; N]; N]) -> bool {
    if move_num == (N * N) as i32 {
        return true;
    }

    for i in 0..8 {
        let next_x = x + moves_x[i];
        let next_y = y + moves_y[i];

        if is_safe(next_x, next_y, board) {
            board[next_x as usize][next_y as usize] = move_num;
            if solve_knight_tour(next_x, next_y, move_num + 1, board) {
                return true;
            } else {
                board[next_x as usize][next_y as usize] = -1; // 백트래킹
            }
        }
    }
    false
}

fn knight_tour() {
    board[0][0] = 0;
    if !solve_knight_tour(0, 0, 1, &mut board) {
        println!("해결할 수 없습니다.");
    } else {
        for row in board.iter() {
            for cell in row.iter() {
                print!("{:3} ", cell);
            }
            println!();
        }
    }
}

knight_tour();

이 Rust 코드도 같은 방식으로 작동하며, 재귀와 백트래킹을 사용하여 문제를 해결합니다.


사용 사례

  • 경로 탐색 문제: 게임 또는 로봇이 경로를 최적으로 탐색하는 데 사용됩니다.
  • 그래프 이론 학습: 노드 간의 관계와 경로 탐색 알고리즘을 학습하는 데 유용합니다.

Explanation of Knight’s Tour

The Knight’s Tour problem involves finding a path for a knight on a chessboard that visits every square exactly once according to certain rules. The knight moves in an L-shape in chess, moving two squares in one direction and then one square perpendicular to that direction.

Purpose of the Problem

  • The goal is to find a route that allows the knight to visit every square on the chessboard exactly once.
  • This problem can be solved using algorithms like Backtracking or Warnsdorff’s Rule.

Advantages

  • Path exploration and optimization: It can be applied to game scenarios or path optimization problems and is useful for learning exploration algorithms.
  • Mathematical interest: It can be extended to combinatorial problems and graph theory, providing training in mathematical approaches.

Disadvantages

  • Complexity: The amount of computation required to solve larger chessboards can be very high.
  • Dependence on Backtracking: The backtracking method may not be efficient as it requires several attempts.

JavaScript Example Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const N = 8;
let board = Array.from({ length: N }, () => Array(N).fill(-1));

const movesX = [2, 1, -1, -2, -2, -1, 1, 2];
const movesY = [1, 2, 2, 1, -1, -2, -2, -1];

function isSafe(x, y, board) {
  return x >= 0 && x < N && y >= 0 && y < N && board[x][y] === -1;
}

function solveKnightTour(x, y, move, board) {
  if (move === N * N) return true;

  for (let i = 0; i < 8; i++) {
    const nextX = x + movesX[i];
    const nextY = y + movesY[i];

    if (isSafe(nextX, nextY, board)) {
      board[nextX][nextY] = move;
      if (solveKnightTour(nextX, nextY, move + 1, board)) {
        return true;
      } else {
        board[nextX][nextY] = -1; // Backtracking
      }
    }
  }
  return false;
}

function knightTour() {
  board[0][0] = 0; // Starting position
  if (!solveKnightTour(0, 0, 1, board)) {
    console.log("Cannot solve.");
  } else {
    console.log(board);
  }
}

knightTour();

This code solves the Knight’s Tour using a backtracking algorithm. The solveKnightTour function recursively explores possible paths and backtracks if necessary.


Rust Example Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const N: usize = 8;
let mut board = [[-1; N]; N];

let moves_x: [isize; 8] = [2, 1, -1, -2, -2, -1, 1, 2];
let moves_y: [isize; 8] = [1, 2, 2, 1, -1, -2, -2, -1];

fn is_safe(x: isize, y: isize, board: &[[i32; N]; N]) -> bool {
    x >= 0 && x < N as isize && y >= 0 && y < N as isize && board[x as usize][y as usize] == -1
}

fn solve_knight_tour(x: isize, y: isize, move_num: i32, board: &mut [[i32; N]; N]) -> bool {
    if move_num == (N * N) as i32 {
        return true;
    }

    for i in 0..8 {
        let next_x = x + moves_x[i];
        let next_y = y + moves_y[i];

        if is_safe(next_x, next_y, board) {
            board[next_x as usize][next_y as usize] = move_num;
            if solve_knight_tour(next_x, next_y, move_num + 1, board) {
                return true;
            } else {
                board[next_x as usize][next_y as usize] = -1; // Backtracking
            }
        }
    }
    false
}

fn knight_tour() {
    board[0][0] = 0;
    if !solve_knight_tour(0, 0, 1, &mut board) {
        println!("Cannot solve.");
    } else {
        for row in board.iter() {
            for cell in row.iter() {
                print!("{:3} ", cell);
            }
            println!();
        }
    }
}

knight_tour();

This Rust code operates in the same manner, using recursion and backtracking to solve the problem.


Use Cases

  • Pathfinding problems: It can be used in games or robotics to optimally explore paths.
  • Graph theory learning: It is useful for learning about relationships between nodes and pathfinding algorithms.