2. 騎士巡歴問題
著者:梅谷 武
騎士巡歴問題の解法を実装する。
作成:2024-06-01
更新:2024-10-29
騎士巡歴問題きしじゅんれきもんだい, knight's tour problem(knight's tour problem)とは8×8のチェス盤上をナイトを移動させてすべての枡目を一回ずつ通過させるという問題である。ナイトとは将棋の桂馬と同じ動きを前後左右の四方向にできるチェスの駒である。
 チェス盤を8×8行列boardで表わし、-1で初期化しておく。ナイトの初期位置は(0,0)に固定する。ナイトが何回動いたかをcountで表わす。次の状態は可能な8個の枡目で有効なものの中からいずれかを選び、count = count + 1とし、その枡目にcountを書き込む。これを繰り返してcountが63になったとき解が求まっている。この最初の解が求まったら終了する。プログラムは探索空間を木と考えたときの深さ優先探索(バックトラック法)で書く。

C++

 ktour.cpp:
#include <print>
#include <vector>
 
const int N = 8;
const int moveX[8] = {2, 1, -1, -2, -2, -1, 1, 2};
const int moveY[8] = {1, 2, 2, 1, -1, -2, -2, -1};
 
bool isValidMove(int x, int y, const std::vector<std::vector<int>> &board) {
  return (x >= 0 && x < N && y >= 0 && y < N && board[x][y] == -1);
}
 
bool KnightTour(int x, int y, int count, std::vector<std::vector<int>> &board) {
  if (count == N * N)
    return true;
  for (int k = 0; k < N; ++k) {
    int nextX = x + moveX[k];
    int nextY = y + moveY[k];
    if (isValidMove(nextX, nextY, board)) {
      board[nextX][nextY] = count;
      if (KnightTour(nextX, nextY, count + 1, board))
        return true;
      else
        board[nextX][nextY] = -1;
    }
  }
  return false;
}
 
void printBoard(const std::vector<std::vector<int>> &board) {
  for (const auto &row : board) {
    for (const auto &cell : row)
      std::print("{}\t", cell);
    std::print("\n");
  }
}
 
int main() {
  std::vector<std::vector<int>> board(N, std::vector<int>(N, -1));
  board[0][0] = 0;
  if (KnightTour(0, 0, 1, board))
    printBoard(board);
  else
    std::println("No solution exists.");
  return 0;
}


 makefile:
CPPG = g++
CPPL = clang++
CPPFLAGS = -std=c++23 -O2
SRC = ktour.cpp
EXES = ktour_g ktour_l
 
all: $(EXES)
 
ktour_g: $(SRC)
	$(CPPG) $(CPPFLAGS) -o $@ $(SRC)
 
ktour_l: $(SRC)
	$(CPPL) $(CPPFLAGS) -o $@ $(SRC)
 
clean:
	rm -f $(EXES)


$ measure ./ktour_g
0       59      38      33      30      17      8       63
37      34      31      60      9       62      29      16
58      1       36      39      32      27      18      7
35      48      41      26      61      10      15      28
42      57      2       49      40      23      6       19
47      50      45      54      25      20      11      14
56      43      52      3       22      13      24      5
51      46      55      44      53      4       21      12
======================================
Process exited with status: 0
total time:  0.233499 [sec]
mem  size:       3676 [KB]
code size:        118 [KB]
$ measure ./ktour_l
0       59      38      33      30      17      8       63
37      34      31      60      9       62      29      16
58      1       36      39      32      27      18      7
35      48      41      26      61      10      15      28
42      57      2       49      40      23      6       19
47      50      45      54      25      20      11      14
56      43      52      3       22      13      24      5
51      46      55      44      53      4       21      12
======================================
Process exited with status: 0
total time:  0.225330 [sec]
mem  size:       3696 [KB]
code size:         92 [KB]
 ktour.ml:
open Printf
 
let n = 8
let moveX = [|2; 1; -1; -2; -2; -1; 1; 2|]
let moveY = [|1; 2; 2; 1; -1; -2; -2; -1|]
 
let is_valid_move x y board =
  x >= 0 && x < n && y >= 0 && y < n && board.(x).(y) = -1
 
let rec knight_tour x y count board =
  if count = n * n then true
  else
    let rec try_moves k =
      if k = n then false
      else
        let nextX = x + moveX.(k) in
        let nextY = y + moveY.(k) in
        if is_valid_move nextX nextY board then
          begin
            board.(nextX).(nextY) <- count;
            if knight_tour nextX nextY (count + 1) board then true
            else
              begin
                board.(nextX).(nextY) <- -1;
                try_moves (k + 1)
              end
          end
        else
          try_moves (k + 1)
    in
    try_moves 0
 
let print_board board =
  Array.iter (fun row ->
    Array.iter (fun cell ->
      printf "%d\t" cell
    ) row;
    printf "\n"
  ) board
 
let () =
  let board = Array.make_matrix n n (-1) in
  board.(0).(0) <- 0;
  if knight_tour 0 0 1 board then
    print_board board
  else
    printf "No solution exists.\n"


$ ocamlopt -O2 -o ktour ktour.ml
$ measure ./ktour
0       59      38      33      30      17      8       63
37      34      31      60      9       62      29      16
58      1       36      39      32      27      18      7
35      48      41      26      61      10      15      28
42      57      2       49      40      23      6       19
47      50      45      54      25      20      11      14
56      43      52      3       22      13      24      5
51      46      55      44      53      4       21      12
======================================
Process exited with status: 0
total time:  0.514517 [sec]
mem  size:       5496 [KB]
code size:       2343 [KB]
 ktour.rkt:
#lang racket
(require iso-printf)
 
(define n 8)
(define moveX (vector 2 1 -1 -2 -2 -1 1 2))
(define moveY (vector 1 2 2 1 -1 -2 -2 -1))
 
(define (is-valid-move? x y board)
  (and (>= x 0) (< x n) (>= y 0) (< y n) (= (vector-ref (vector-ref board x) y) -1)))
 
(define (try-moves k x y count board)
  (if (= k n)
    #f
    (let* ([nextX (+ x (vector-ref moveX k))]
           [nextY (+ y (vector-ref moveY k))])
      (if (is-valid-move? nextX nextY board)
        (begin
          (vector-set! (vector-ref board nextX) nextY count)
          (if (knight-tour nextX nextY (+ count 1) board)
            #t
            (begin
              (vector-set! (vector-ref board nextX) nextY -1)
              (try-moves (+ k 1) x y count board))))
        (try-moves (+ k 1) x y count board)))))
 
(define (knight-tour x y count board)
  (if (= count (* n n))
    #t
    (try-moves 0 x y count board)))
 
(define (print-board board)
  (for ([i n])
    (for ([j n])
      (printf "%d\t" (vector-ref (vector-ref board i) j)))
    (printf "\n")))
 
(define board (for/vector ([i n]) (make-vector n -1)))
(vector-set! (vector-ref board 0) 0 0)
(if (knight-tour 0 0 1 board)
  (print-board board)
  (printf "No solution exists.\n"))


$ raco exe ktour.rkt
$ measure ./ktour
0       59      38      33      30      17      8       63
37      34      31      60      9       62      29      16
58      1       36      39      32      27      18      7
35      48      41      26      61      10      15      28
42      57      2       49      40      23      6       19
47      50      45      54      25      20      11      14
56      43      52      3       22      13      24      5
51      46      55      44      53      4       21      12
======================================
Process exited with status: 0
total time:  1.425365 [sec]
mem  size:     133788 [KB]
code size:      12446 [KB]
 測定結果を表にまとめる。単位は秒[sec]。
測定項目 GCC Clang OCaml Racket
knight's tour 0.233 0.225 0.515 1.425
[1] Wikipedia, Knight's tour, Wikimedia Foundation, Inc., 2024
語  句
騎士巡歴問題 きしじゅんれきもんだい, knight's tour problem
8×8のチェス盤上をナイトを移動させてすべての枡目を一回ずつ通過させるという問題のこと
 
This document is licensed under the MIT License.
Copyright (C) 2024 Takeshi Umetani