📜  骑士巡回问题的Warnsdorff算法

📅  最后修改于: 2021-05-24 20:42:42             🧑  作者: Mango

问题:骑士被放置在一个空棋盘的第一块上,并且根据国际象棋的规则移动,每个方块必须正好访问一次。

以下是Knight覆盖所有单元的示例路径。下面的网格表示一个8 x 8格的棋盘。单元格中的数字表示骑士的移动次数。
骑士旅行问题

我们已经讨论了奈特之旅的回溯算法。本文讨论了Warnsdorff的启发式方法。

Warnsdorff的规则:

  1. 我们可以从骑士在棋盘上的任何初始位置开始。
  2. 我们总是以最小的程度(最少的未访问相邻数)移动到相邻的未访问正方形。

该算法也可以更普遍地应用于任何图形。

一些定义:

  • 如果P可以通过一个骑士的动作移动到Q,并且尚未访问Q,则可以从P位置访问Q。
  • 位置P的可访问性是可从P访问的位置数。

算法:

  1. 将P设置为板上的随机初始位置
  2. 用移动号“ 1”将板标记在P上
  3. 对于从2到板上的平方数的每个移动编号,请执行以下操作:
    • 令S为可从P访问的位置集合。
    • 将P设置为S中具有最小可及性的位置
    • 用当前的移动编号将板标记在P上
  4. 返回标有标记的木板-每个方块都会标有其访问所在的搬家号码。

下面是上述算法的实现。

C++
// C++ program to for Kinight's tour problem usin
// Warnsdorff's algorithm
#include 
#define N 8
  
// Move pattern on basis of the change of
// x coordinates and y coordinates respectively
static int cx[N] = {1,1,2,2,-1,-1,-2,-2};
static int cy[N] = {2,-2,1,-1,2,-2,1,-1};
  
// function restricts the knight to remain within
// the 8x8 chessboard
bool limits(int x, int y)
{
    return ((x >= 0 && y >= 0) && (x < N && y < N));
}
  
/* Checks whether a square is valid and empty or not */
bool isempty(int a[], int x, int y)
{
    return (limits(x, y)) && (a[y*N+x] < 0);
}
  
/* Returns the number of empty squares adjacent
   to (x, y) */
int getDegree(int a[], int x, int y)
{
    int count = 0;
    for (int i = 0; i < N; ++i)
        if (isempty(a, (x + cx[i]), (y + cy[i])))
            count++;
  
    return count;
}
  
// Picks next point using Warnsdorff's heuristic.
// Returns false if it is not possible to pick
// next point.
bool nextMove(int a[], int *x, int *y)
{
    int min_deg_idx = -1, c, min_deg = (N+1), nx, ny;
  
    // Try all N adjacent of (*x, *y) starting
    // from a random adjacent. Find the adjacent
    // with minimum degree.
    int start = rand()%N;
    for (int count = 0; count < N; ++count)
    {
        int i = (start + count)%N;
        nx = *x + cx[i];
        ny = *y + cy[i];
        if ((isempty(a, nx, ny)) &&
           (c = getDegree(a, nx, ny)) < min_deg)
        {
            min_deg_idx = i;
            min_deg = c;
        }
    }
  
    // IF we could not find a next cell
    if (min_deg_idx == -1)
        return false;
  
    // Store coordinates of next point
    nx = *x + cx[min_deg_idx];
    ny = *y + cy[min_deg_idx];
  
    // Mark next move
    a[ny*N + nx] = a[(*y)*N + (*x)]+1;
  
    // Update next point
    *x = nx;
    *y = ny;
  
    return true;
}
  
/* displays the chessboard with all the
  legal knight's moves */
void print(int a[])
{
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
            printf("%d\t",a[j*N+i]);
        printf("\n");
    }
}
  
/* checks its neighbouring sqaures */
/* If the knight ends on a square that is one
   knight's move from the beginning square,
   then tour is closed */
bool neighbour(int x, int y, int xx, int yy)
{
    for (int i = 0; i < N; ++i)
        if (((x+cx[i]) == xx)&&((y + cy[i]) == yy))
            return true;
  
    return false;
}
  
/* Generates the legal moves using warnsdorff's
  heuristics. Returns false if not possible */
bool findClosedTour()
{
    // Filling up the chessboard matrix with -1's
    int a[N*N];
    for (int i = 0; i< N*N; ++i)
        a[i] = -1;
  
    // Randome initial position
    int sx = rand()%N;
    int sy = rand()%N;
  
    // Current points are same as initial points
    int x = sx, y = sy;
    a[y*N+x] = 1; // Mark first move.
  
    // Keep picking next points using
    // Warnsdorff's heuristic
    for (int i = 0; i < N*N-1; ++i)
        if (nextMove(a, &x, &y) == 0)
            return false;
  
    // Check if tour is closed (Can end
    // at starting point)
    if (!neighbour(x, y, sx, sy))
        return false;
  
    print(a);
    return true;
}
  
// Driver code
int main()
{
    // To make sure that different random
    // initial positions are picked.
    srand(time(NULL));
  
    // While we don't get a solution
    while (!findClosedTour())
    {
    ;
    }
  
    return 0;
}


Java
// Java program to for Kinight's tour problem using
// Warnsdorff's algorithm
import java.util.concurrent.ThreadLocalRandom;
  
class GFG 
{
    public static final int N = 8;
  
    // Move pattern on basis of the change of
    // x coordinates and y coordinates respectively
    public static final int cx[] = {1, 1, 2, 2, -1, -1, -2, -2};
    public static final int cy[] = {2, -2, 1, -1, 2, -2, 1, -1};
  
    // function restricts the knight to remain within
    // the 8x8 chessboard
    boolean limits(int x, int y)
    {
        return ((x >= 0 && y >= 0) &&
                 (x < N && y < N));
    }
  
    /* Checks whether a square is valid and
    empty or not */
    boolean isempty(int a[], int x, int y) 
    {
        return (limits(x, y)) && (a[y * N + x] < 0);
    }
  
    /* Returns the number of empty squares 
    adjacent to (x, y) */
    int getDegree(int a[], int x, int y) 
    {
        int count = 0;
        for (int i = 0; i < N; ++i)
            if (isempty(a, (x + cx[i]), 
                           (y + cy[i])))
                count++;
  
        return count;
    }
  
    // Picks next point using Warnsdorff's heuristic.
    // Returns false if it is not possible to pick
    // next point.
    Cell nextMove(int a[], Cell cell) 
    {
        int min_deg_idx = -1, c, 
            min_deg = (N + 1), nx, ny;
  
        // Try all N adjacent of (*x, *y) starting
        // from a random adjacent. Find the adjacent
        // with minimum degree.
        int start = ThreadLocalRandom.current().nextInt(1000) % N;
        for (int count = 0; count < N; ++count)
        {
            int i = (start + count) % N;
            nx = cell.x + cx[i];
            ny = cell.y + cy[i];
            if ((isempty(a, nx, ny)) &&
                (c = getDegree(a, nx, ny)) < min_deg)
            {
                min_deg_idx = i;
                min_deg = c;
            }
        }
  
        // IF we could not find a next cell
        if (min_deg_idx == -1)
            return null;
  
        // Store coordinates of next point
        nx = cell.x + cx[min_deg_idx];
        ny = cell.y + cy[min_deg_idx];
  
        // Mark next move
        a[ny * N + nx] = a[(cell.y) * N + 
                           (cell.x)] + 1;
  
        // Update next point
        cell.x = nx;
        cell.y = ny;
  
        return cell;
    }
  
    /* displays the chessboard with all the
    legal knight's moves */
    void print(int a[])
    {
        for (int i = 0; i < N; ++i) 
        {
            for (int j = 0; j < N; ++j)
                System.out.printf("%d\t", a[j * N + i]);
            System.out.printf("\n");
        }
    }
  
    /* checks its neighbouring sqaures */
    /* If the knight ends on a square that is one
    knight's move from the beginning square,
    then tour is closed */
    boolean neighbour(int x, int y, int xx, int yy) 
    {
        for (int i = 0; i < N; ++i)
            if (((x + cx[i]) == xx) && 
                ((y + cy[i]) == yy))
                return true;
  
        return false;
    }
  
    /* Generates the legal moves using warnsdorff's
    heuristics. Returns false if not possible */
    boolean findClosedTour() 
    {
        // Filling up the chessboard matrix with -1's
        int a[] = new int[N * N];
        for (int i = 0; i < N * N; ++i)
            a[i] = -1;
  
        // initial position
        int sx = 3;
        int sy = 2;
  
        // Current points are same as initial points
        Cell cell = new Cell(sx, sy);
  
        a[cell.y * N + cell.x] = 1; // Mark first move.
  
        // Keep picking next points using
        // Warnsdorff's heuristic
        Cell ret = null;
        for (int i = 0; i < N * N - 1; ++i)
        {
            ret = nextMove(a, cell);
            if (ret == null)
                return false;
        }
  
        // Check if tour is closed (Can end
        // at starting point)
        if (!neighbour(ret.x, ret.y, sx, sy))
            return false;
  
        print(a);
        return true;
    }
  
    // Driver Code
    public static void main(String[] args)
    {
        // While we don't get a solution
        while (!new GFG().findClosedTour())
        {
            ;
        }
    }
}
  
class Cell 
{
    int x;
    int y;
  
    public Cell(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}
  
// This code is contributed by SaeedZarinfam


输出:

59    14    63    32    1    16    19    34    
62    31    60    15    56    33    2    17    
13    58    55    64    49    18    35    20    
30    61    42    57    54    51    40    3    
43    12    53    50    41    48    21    36    
26    29    44    47    52    39    4    7    
11    46    27    24    9    6    37    22    
28    25    10    45    38    23    8    5    



哈密顿路径问题通常是NP难的。在实践中,沃恩斯多夫的启发式方法成功地找到了线性时间的解决方案。

你知道吗?
“在一个8×8的木板上,恰好有26,534,728,821,064个定向封闭巡回路线(即,沿着同一路径沿相反方向行驶的两个巡回路线分别计算在内,旋转和反射也是如此)。无方向的封闭式游览的数量是该数字的一半,因为每个游览都可以反向追踪!”