12个主宰骑士拼图(回溯)

时间:2016-04-10 07:57:17

标签: c++ recursion backtracking chess

我一直在寻找好几个小时,但尚未找到适合此类谜题的全面解决方案。所以我跟主教们有类似的问题。

我需要做的就是在国际象棋棋盘上放置12个骑士,使棋盘的所有自由方格都被至少一件攻击。

最终结果应如下所示:

enter image description here

问题是 ,我的程序只尝试与最后两个部分的不同组合然后以某种方式崩溃。 已编辑

到目前为止我做了什么:

#include <iostream>
using namespace std;
#define N 8

void fillChessBoard(int (&chessBoard)[N][N], int num);
void printChessBoard(int (&chessBoard)[N][N]);
void removeKnight(int (&chessBoard)[N][N], int i, int j);
void placeKnight(int (&chessBoard)[N][N], int i, int j);
bool allSpaceDominated(int (&chessBoard)[N][N]);
bool backtracking(int (&chessBoard)[N][N], int pos);

int main()
{
    int chessBoard[N][N];

    fillChessBoard(chessBoard, 0);
    backtracking(chessBoard, 0);

    return 0;
}

bool backtracking(int (&chessBoard)[N][N], int knightNum)
{
    if(knightNum==12)
    {
        if(allSpaceDominated(chessBoard))
        {
            printChessBoard(chessBoard);
            return true;
        }
        else return false;
    }
    else
    {
        for(int i=0; i<N; i++)
        {
            for(int j=0; j<N; j++)
            {
                if(chessBoard[i][j]!=2)
                {
                    placeKnight(chessBoard, i, j);
                    printChessBoard(chessBoard); //step by step

                    if(backtracking(chessBoard, knightNum+1)) return true;
                    removeKnight(chessBoard, i, j);
                }
            }
        }
    }
}
void fillChessBoard(int (&chessBoard)[N][N], int num)
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            chessBoard[i][j]=num;
        }
    }
}
void printChessBoard(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            cout<<chessBoard[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
    cout<<endl;
    cin.get();
}
void removeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=0;
    chessBoard[i][j]=num;

    if(i+2 <= N && j+1 <= N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 <= N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 <= N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 <= N && j+2 <= N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 <= N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 <= N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;

    for(int k=0; k<N; k++) //correct overlapping dominations
    {
        for(int x=0; x<N; x++)
        {
            if(chessBoard[k][x]==2)
            {
                placeKnight(chessBoard, k, x);
            }
        }
    }
}
void placeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=1;
    chessBoard[i][j]=2;

    if(i+2 <= N && j+1 <= N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 <= N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 <= N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 <= N && j+2 <= N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 <= N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 <= N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;
}
bool allSpaceDominated(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            if(chessBoard[i][j]==0) return false;
        }
    }
    return true;
}

修改

  • 修复了placeKnightremoveKnight中的数组边界(例如,j + 2 &LT; N代替j + 2&lt; = N)
  • return false;
  • 中添加了backtracking

问题: 现在它永远循环。尝试了大量的组合,但从未找到我需要的组合(暴力?)

#include <iostream>
using namespace std;
#define N 8

void fillChessBoard(int (&chessBoard)[N][N], int num);
void printChessBoard(int (&chessBoard)[N][N]);
void removeKnight(int (&chessBoard)[N][N], int i, int j);
void placeKnight(int (&chessBoard)[N][N], int i, int j);
bool allSpaceDominated(int (&chessBoard)[N][N]);
bool backtracking(int (&chessBoard)[N][N], int pos);

int main()
{
    int chessBoard[N][N];

    fillChessBoard(chessBoard, 0);
    backtracking(chessBoard, 0);
    printChessBoard(chessBoard);

    return 0;
}
bool backtracking(int (&chessBoard)[N][N], int knightNum)
{
    if(knightNum==12)
    {
        if(allSpaceDominated(chessBoard))
        {
            printChessBoard(chessBoard);
            return true;
        }
        else return false;
    }
    else
    {
        for(int i=0; i<N; i++)
        {
            for(int j=0; j<N; j++)
            {
                if(chessBoard[i][j]!=2)
                {
                    placeKnight(chessBoard, i, j);
                    printChessBoard(chessBoard);// step by step
                    if(backtracking(chessBoard, knightNum+1)) return true;

                    removeKnight(chessBoard, i, j);
                }
            }
        }
        return false; //ADDED LINE
    }
}
void fillChessBoard(int (&chessBoard)[N][N], int num)
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            chessBoard[i][j]=num;
        }
    }
}
void printChessBoard(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            cout<<chessBoard[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
    cout<<endl;
    //cin.get();
}
void removeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=0;
    chessBoard[i][j]=num;

    if(i+2 < N && j+1 < N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 < N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 < N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 < N && j+2 < N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 < N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 < N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;

    for(int k=0; k<N; k++)
    {
        for(int x=0; x<N; x++)
        {
            if(chessBoard[k][x]==2)
            {
                placeKnight(chessBoard, k, x);
            }
        }
    }
}
void placeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=1;
    chessBoard[i][j]=2;

    if(i+2 < N && j+1 < N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 < N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 < N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 < N && j+2 < N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 < N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 < N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;
}
bool allSpaceDominated(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            if(chessBoard[i][j]==0) return false;
        }
    }
    return true;
}

5 个答案:

答案 0 :(得分:15)

您的尝试效率非常低,因此可能只是因为您无法找到解决方案的效率低下。

首先,尝试放置12个骑士毫无意义。将6个骑士放在白色的田野上。找到所有解决方案然后任何在白色领域拥有6个骑士的解决方案都可以进行镜像,并在黑色领域提供6个骑士,并将其结合起来。

其次,你试图以任何顺序放置骑士。但是顺序是任意的。所以把它们放在一个有序的顺序中,比如a1,c1,e1,g1,b2,d2,f2,h2,a3 ......等。这减少了因子6的选择数量!或720(原始情况下12!=数十亿)。

要高效:将白色字段从0到31编号。将0到31的黑色字段编号。对于每个黑色字段,找到该字段上骑士可以到达的白色字段的索引,并创建表示这些字段的32位位图。

然后:

for (int k1 = 0; k1 < 27; ++k1)
    for (int k2 = k1+1, k2 < 28; ++k2)
        for (int k3 = k2+1; k3 < 29; ++k3)
            for (int k4 = k3+1; k4 < 30; ++k4)
                for (int k5 = k4+1; k5 < 31; ++k5)
                   for (int k6 = k5+1; k6 < 32; ++k6)
                       if ((bits [k1] | bits [k2] | bits [k3] | bits [k4] | bits [k5] | bits [k6]) == 0xffffffff)
                           // got a solution!!!

这不到一百万次检查,所以需要几毫秒。

PS。你的placeKnight / removeKnight的组合不起作用。例如,c3被b1或a2上的骑士所覆盖。如果你将骑士放在a2上,然后放在b1上,然后移除b1上的骑士,你将c3设置为“未覆盖”。

PS。如果你有一个更大的棋盘,你会采取捷径来减少可能性。例如,字段a1必须由第一行,第二行上的骑士或第三行上的b3覆盖。因此,如果你试图将骑士放在c3或更高的战场上,并且没有覆盖a1,则根本不需要在该战场或后期战场上设置骑士。

答案 1 :(得分:3)

正如@ gnasher729所指出的,试图将所有12个骑士放置在效率非常低的情况下,我们可以尝试在white blocks上放置6个骑士,但是使用这种方法我们只能攻击最多30 black blocks out of 32 black blocks 1}}。

因此,从上面我们可以采取两种方法:

  

1)我们可以在剩余的2个黑色区块上修复2个骑士,然后尝试将剩下的4个骑士放置在剩余的30个黑色区块上,现在注意我们只需要攻击其余的26个白色区块。

@ gnasher729说我们可以反映解决方案,但是我无法想出修复2个地方然后找到镜子的逻辑,因为只有30个街区受到攻击,骑士的数量是14,然后所有32个街区都会受到攻击,找到一面镜子也许是可能的。

  

2)当我们找到攻击超过26 6 X 6的前6个骑士的解决方案时,第二个是蛮力剩下的6个骑士,我实施但仍未找到解决方案。

所以@n.m.说我们可以尝试从中心找到解决方案以减少搜索空间,所以我试图通过将骑士放在#include <iostream> #include <ctime> using namespace std; #define N 8 int board[N][N], mark[N][N]; void placeOnBoard(int i, int j){ int count = 0; if(mark[i][j] == 0) mark[i][j] = 1; if(i+2 < N && j+1 < N && mark[i+2][j+1] == 0) mark[i+2][j+1] = 1; if(i+2 < N && j-1 >= 0 && mark[i+2][j-1] == 0) mark[i+2][j-1] = 1; if(i-2 >= 0 && j+1 < N && mark[i-2][j+1] == 0) mark[i-2][j+1] = 1; if(i-2 >= 0 && j-1 >= 0 && mark[i-2][j-1] == 0) mark[i-2][j-1] = 1; if(j+2 < N && i+1 < N && mark[i+1][j+2] == 0) mark[i+1][j+2] = 1; if(j+2 < N && i-1 >= 0 && mark[i-1][j+2] == 0) mark[i-1][j+2] = 1; if(j-2 >= 0 && i+1 < N && mark[i+1][j-2] == 0) mark[i+1][j-2] = 1; if(j-2 >= 0 && i-1 >= 0 && mark[i-1][j-2] == 0) mark[i-1][j-2] = 1; } void printBoard(){ for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++){ if(board[i][j] != 0) cout << "K "; else cout << board[i][j] << " "; } cout << endl; } cout << endl; } void backtrackBlack(int knightNum, int currX, int currY){ if(knightNum == 7){ int count = 0; for(int i = 0;i < N;i++) for(int j = 0;j < N;j++) mark[i][j] = 0; for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++) if(board[i][j] != 0) placeOnBoard(i, j); } for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++) if(mark[i][j] != 0) count++; } if(count == 64) printBoard(); return; } if(currX == N-1 && currY == N) return; int newX, newY; //new place in the board to move to if(currY == N) newY = 0,newX = currX + 1; else newY = currY + 1,newX = currX; //do not place the current knight at (currX, currY) backtrackBlack(knightNum, newX, newY); //try to place the current knight at (currX, currY) if((currX + currY) % 2 == 1 && currX > 0 && currX < N-1 && currY > 0 && currY < N-1){ board[currX][currY] = knightNum; backtrackBlack(knightNum+1, newX, newY); board[currX][currY] = 0; } } void backtrackWhite(int knightNum, int currX, int currY){ if(knightNum == 7){ int count = 0; for(int i = 0;i < N;i++) for(int j = 0;j < N;j++) mark[i][j] = 0; for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++) if(board[i][j] != 0) placeOnBoard(i, j); } for(int i = 0;i < N;i++){ for(int j = 0;j < N;j++) if(mark[i][j] != 0) count++; } if(count >= 32){ backtrackBlack(1, 0, 0); //printBoard(); } return; } if(currX == N-1 && currY == N) return; int newX, newY; //new place in the board to move to if(currY == N) newY = 0,newX = currX + 1; else newY = currY + 1,newX = currX; //do not place the current knight at (currX, currY) backtrackWhite(knightNum, newX, newY); //try to place the current knight at (currX, currY) if((currX + currY) % 2 == 0 && currX > 0 && currX < N-1 && currY > 0 && currY < N-1){ board[currX][currY] = knightNum; backtrackWhite(knightNum+1, newX, newY); board[currX][currY] = 0; } } int main(){ time_t t = clock(); backtrackWhite(1, 0, 0); t = clock() - t; double time_taken = ((double)t)/CLOCKS_PER_SEC; cout << "Time Taken : " << time_taken<< endl; return 0; } 中心方块找到解决方案,并且进一步只在32个黑色块中的30个中寻找解决方案被攻击而不是26,最后能够找到两个对称问题的解决方案,可能有更多解决方案可用于更好的方法。

c ++中的代码:

0 0 0 0 0 0 0 0
0 0 K 0 0 0 0 0
0 0 K K 0 K K 0
0 0 0 0 0 K 0 0
0 0 K 0 0 0 0 0
0 K K 0 K K 0 0
0 0 0 0 0 K 0 0
0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0
0 0 0 0 0 K 0 0
0 K K 0 K K 0 0
0 0 K 0 0 0 0 0
0 0 0 0 0 K 0 0
0 0 K K 0 K K 0
0 0 K 0 0 0 0 0
0 0 0 0 0 0 0 0

Time Taken : 89.2418

它只能在大约89秒内找到2个解决方案。

输出:

Java

答案 2 :(得分:2)

这是一种相当有效的方法;你的电路板是[8] [8]的数组或[64]的单个数组。你有12件要放置。在我们开始编写任何代码来实现程序来解决这个问题之前,让我们首先评估一下情况并设计一个算法。

首先我们可以做两件事,我们可以省略或消除我们已经知道的那些是骑士不能满足这个问题的解决方案的地方。这些是电路板的外部电池或边界,四个对角相邻的内角,以及构成电路板死点的4个电池或瓷砖。如果任何一个骑士被放置在这些方格中的任何一个上,则该解决方案将不起作用。我们还可以看一下4的最小公分母,并且我们可以将电路板划分为4个象限,并且在该象限中仅使用3个。

这有两件事;一,它使算法更容易定义和更有效,它也满足另一个条件。其他条件是这样,我们必须在每个象限中有3个骑士,以使解决方案有效。如果任何一个象限中有4个或更多,并且在任何象限中都少于3个,则解决方案将再次失败。

如果我们查看每个象限,我们可以看到:

  • 左上象限 - 右下角是中心细胞之一。
  • 右上象限 - 左下单元格是中心单元格之一。
  • 左下象限 - 右上角单元格是中心单元格之一。
  • 右下象限 - 左上角单元格是中心单元格之一。

是什么让这至关重要?我们知道当把一个骑士放在棋盘上让任何开放的牢房被攻击时,在这四个象限的棋盘正中心的这4个方格不能在他们的位置上有一个骑士并且至少有一个在水平或垂直方向上向外相邻的细胞必须有一个骑士。知道这一点,我们可以在1象限上放置3个骑士,立即排除我们刚刚标记的细胞和与同一个中心细胞相邻的另一个细胞。

如果您解决其中一个象限,那么其他三个象征只是它们的翻译。因此,采用这种方法,只需要进行很多计算即可解决一个4x4网格,其内角被列为起点,其水平或垂直邻居将有一个骑士放置,而哪一个有骑士放置另一个相邻将留空。下面是一个可以直观地查看此消除过程的图表,以便您了解如何正确构建或实施检查,搜索和放置算法。

Knights

因此,一旦能够看到这一点,并知道问题发生了什么。这些是您的算法中的步骤。

  • 分割 - 4个象限 - 每个象限3个骑士
  • 消除或跳过无效的单元格(边框,内角和中心单元格)
  • 将中心单元放置在垂直或水平位置。
  • 有7个可能的单元格可供选择进行放置,但由于选择了一个而另一个相邻,我们现在有5个单元格可以放置2个骑士。
  • 解决此象限的其余部分。
  • 在上述解决方案中执行对称。如果这是象限1,那么我们不需要解决象限4,我们可以采用所有解决方案并执行+对角线对称。如果这是象限2,那么我们不需要求解象限3,我们可以执行 - 对角线对称。
  • 现在将所有4个象限拼接回所有解决方案并发送 检查器的每个解决方案,以验证它是否满足可攻击条件。这应该是通过[64]的数组进行线性搜索,并进行一些比较,相当快。
  • 删除任何不符合要求的解决方案。
  • 打印结果。

修改

这是一个示例程序,演示如何设置在开始解决问题之前准备好的预定义板,并验证解决方案是否正确。

#include <conio.h>
#include <iostream>
#include <iomanip>

// These Enums Are Only A Visual Reference And Are Not Used
enum CellPlacement {
    EMPTY_CELL     = 0, 
    BLOCKED_BORDER = 1, 
    BLOCKED_CENTER = 2,
    KNIGHT_PLACED  = 3, 
};

enum CellColor {
    WHITE = 0,
    WHITE = 1,
};

enum IsAttacked {
    NO = 0,
    YES = 1,
};

struct Cell {
    unsigned char row : 3;      // [0x00, 0x07] 
    unsigned char col : 3;      // [0x00, 0x07]
    unsigned char color : 1;    // [0x00, 0x01] - Refer to CellColor
    unsigned char attacked : 1; // [0x00, 0x01] - Refer to IsAttacked
    unsigned char quad : 3;     // [0x01, 0x04] 
    unsigned char state : 3;    // [0x00, 0x03] - Refer to CellPlacement    
};

struct Board {
    Cell cell[8][8];    
};

struct Quad {
    Cell cell[4][4];
};

struct DividedBoard {
    Quad quad[4];
};


int main() {
    Board board;
    DividedBoard divBoard;

    // Temporary
    unsigned char state = 0x00;
    unsigned char quad  = 0x00;

    for ( unsigned char row = 0; row < 8; row++ ) {
        for ( unsigned char col = 0; col < 8; col++ ) {
            // Place Coords
            board.cell[row][col].row = row;
            board.cell[row][col].col = col;

            // Mark Borders, Inner Corners & Center
            if ( row == 0x00 || row == 0x07 || col == 0x00 || col == 0x07 ) {  // Outer Boarders
                state = 0x01;
                board.cell[row][col].state = state;

            } else if ( (row == 0x01 && col == 0x01) || (row == 0x01 && col == 0x06) ||   // Top Left & Right Inner Adjacent Corners
                        (row == 0x06 && col == 0x01) || (row == 0x06 && col == 0x06) ) {  // Bottom Left & Right Inner Adjacent Corners
                state = 0x01;
                board.cell[row][col].state = state;
            } else if ( (row == 0x03 && col == 0x03) || (row == 0x03 && col == 0x04) ||   // Top Left & Right Centers
                        (row == 0x04 && col == 0x03) || (row == 0x04 && col == 0x04) ) {  // Bottom Left & Right Centers
                state = 0x02;
                board.cell[row][col].state = state;
            } else {
                state = 0x00;
                board.cell[row][col].state = state;  // Empty Cells
            }

            // Mark Wich Quadrant They Belong To And Populate Our Divided Board
            if ( (row >= 0x00 && row < 0x04) && (col >= 0x00 && col < 0x04) ) {
                quad = 0x01;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[0].cell[row][col].row   = row;
                divBoard.quad[0].cell[row][col].col   = col;                
                divBoard.quad[0].cell[row][col].state = state;
                divBoard.quad[0].cell[row][col].quad  = quad;
            }
            if ( (row >= 0x00 && row < 0x04) && (col >= 0x04) ) {
                quad = 0x02;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[1].cell[row][col-4].row   = row;
                divBoard.quad[1].cell[row][col-4].col   = col;          
                divBoard.quad[1].cell[row][col-4].state = state;
                divBoard.quad[1].cell[row][col-4].quad  = quad;
            }
            if ( (row >= 0x04) && (col >= 0x00 && col < 0x04) ) {
                quad = 0x03;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[2].cell[row-4][col].row   = row;
                divBoard.quad[2].cell[row-4][col].col   = col;      
                divBoard.quad[2].cell[row-4][col].state = state;
                divBoard.quad[2].cell[row-4][col].quad  = quad;
            }
            if ( row >= 0x04 && col >= 0x04 ) {
                quad = 0x04;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[3].cell[row-4][col-4].row   = row;
                divBoard.quad[3].cell[row-4][col-4].col   = col;            
                divBoard.quad[3].cell[row-4][col-4].state = state;
                divBoard.quad[3].cell[row-4][col-4].quad  = quad;
            }       
        }
    }

    // Display Board With Blocked & Empty Squares
    std::cout << std::setw(19) << std::setfill('\0') << "Full Board:\n";
    std::cout << std::setw(20) << std::setfill('\0') << "-----------\n\n";
    for ( unsigned char row = 0x00; row < 0x08; row++ ) {
        for ( unsigned char col = 0x00; col < 0x08; col++ ) {
            std::cout << std::setw(2) << +board.cell[row][col].state << " ";
        }
        std::cout << "\n\n";
    }
    std::cout << "\n";


    // Now Print Our Divided Board By Each Quadrant
    for ( unsigned quad = 0; quad < 4; quad++ ) {
        std::cout << std::setw(6) << "Quad" << quad << ":\n\n";
        for ( unsigned row = 0; row < 4; row++ ) {
            for ( unsigned col = 0; col < 4; col++ ) {
                std::cout << std::setw(2) << +divBoard.quad[quad].cell[row][col].state << " ";
            }
            std::cout << "\n\n";
        }
        std::cout << "\n";
    }   

    std::cout << "\nPress any key to quit.\n" << std::endl;
    _getch();
    return 0;
} // main

如果您通过控制台运行此程序,它将基本打印出我之前显示的图像图。如您所见,此处的结构已经创建。在代码中,我将外板标记为值1,将4个内部单元格标记为2,将空单元格标记为0.从这一点开始,这是一个关于获取第一个四边形并从选择两个点中的一个开始的问题。邻近中心,这是一个值为2的单元格。我们[8] [8]中的网格位置为[3] [3],因此您可以使用位置2 [3]或位置{ {3}}开头,如果你设置一个骑士那里的值为3,那么对于这个可能的解决方案,另一个将保持为0。正如你所看到的,只有7个空单元格,在你做出第一个选择后,只剩下5个单元格来选择放置你的第二个骑士,然后剩下4个位置来放置你的第三个和最后一个骑士。

完成此步骤后,您可以对+ 4对称进行反射,使其与Quad 4具有相同的解决方案模式。一旦为Quad 1生成所有这些解决方案,Quad 4也将完成。然后你必须为Quad 2&amp; 3。

因此,如果我们做数学,其中1个骑士被放置,留下2个骑士放置和5个位置这意味着第一个骑士放置有10种可能的解决方案。如果我们将第一个商品放在另一个位置的帐户中,那么我们总共可以为1个象限提供20种可能的解决方案。我们知道有4个象限,所以当你的容器容纳所有四边形时,总共有20 ^ 4个不同的可能解决方案可供选择。这是所有不同可能位置的160,000个总排列数。

我实际上已经提到Quad1的解决方案是Qaud4的反映,Qaud2的解决方案是Quad3的反映。在测试所有解决方案时都是如此,因为正方形被标记为黑色或白色。然而,当涉及到骑士寻找可能的解决方案时,没有一个是相关的,所以不是在这个阶段做对称,我们可以找到1个象限的所有排列,只需从它标记的中心单元旋转它们就能够映射其他3个象限的解决方案。因此,一旦我们找到了Quadrant 1的20种可能布局,只需在所有20种布局上执行3次旋转即可为我们提供80种不同的布局。

然后,混合和匹配这些布局并将其与我们的规则板进行测试是一个问题。

现在这并没有解决问题;但这是解决此问题的有效方法,以最大限度地减少在板上设置字符的排列量。您可以使用此方法设计算法,以测试所有可能的答案,以找到所有正确的解决方案。现在我展示的这些结构是一个良好的开端,但您也可以添加到单元结构中。您可以添加方块所用颜色的标识符,以及另一个标识符,如果它的位置受到攻击。我使用unsigned char,因为当使用一个字节而不是int,甚至是一个short时,内存占用量要小得多,因为所需内容的值范围仅为0-7我也是决定在我的Cell结构中使用一个位域。因此,单个单元的1个单元代替6个字节,现在只有2个字节,剩下几个比特。由于endianess,在使用位字段时需要谨慎,但是,由于所有值都是unsigned char,因此这不应该成为问题。当我们开始对可能的解决方案进行所有排列以找到工作解决方案时,这有助于在四元组结果中构建这些单元格的数组时节省和节省内存空间。此外,通过使用数字而不是实际字母设置单元格值,可以更轻松地进行数学计算。

我没有提到的一件事是:我不是百分之百确定这一点,但是因为我是一名国际象棋手,所以从长时间看板子,当你去做所有的生成过程时可能的解决方案,您可以在将它们发送到函数之前消除它们中的大部分,以验证它们是否满足最终条件,也就是说,我认为在它们的排列中的一个象限中的3个骑士也必须在他们攻击的形状相同。换句话说,它们将在板上形成L形。这意味着如果这三个骑士中的一个没有另一个在水平和垂直位置都相邻的骑士,我们可以得出结论,这个布局不会是有效的布局。当您在单个象限中放置骑士时,可以合并此步骤,然后当您为其他3个象限进行旋转时,您将获得要求的总排列量的一小部分。

并且由于将相邻规则应用于中心单元以及我认为放置的三个骑士必须形成其攻击形状的附加条件,这里是一个图像,显示骑士可以的所有有效位置在。。

3

由于邻近中心放置规则,如果您选择垂直单元格,那么中心的水平单元格将被清空,这使我相信至少有8种可能的解决方案,其中2或4个有效。所以我们甚至更加缩小了我们的搜索和排列范围。我们还可以通过以前的规则来缩小我们的搜索范围,我们可以应用&#34;宾果&#34;这里也要统治。就像在Bingo中一样,中心单元是&#34; Free&#34;对我们来说很好,在每个象限中都没有中心单元,但是从所有可能的位置的交叉模式我们现在知道的中心这个十字架将永远有一个骑士。使用我在全板上使用的坐标和行col的坐标,这些坐标将是[2,2],[2,5],[5,2]和[5,5]。所以在放置阶段,这些可以先自动添加,然后你可以选择相邻的中心,最后你有两个选择留下你的最后一块不是另一个单元也与你的中心单元相邻象限。

在这个额外的情况下,我们已经将整个电路板的总排列数从160,000减少到每象限4个,整个电路板的总排列数为4 ^ 4。因此,如果您有一个预先填充的所有这些可能解决方案的查找表,那么检查有效性的功能只需要调用256次,而不是160,000或数十亿,如果您为所有电路板放置运行它。预先消除是许多人在解决复杂问题之前不考虑的步骤之一。对此有什么好处的是,如果总共有256个可能的排列可以生成一个有效的答案,如果它通过了要求,那么这些排序中的每一个都可以是0-255之间的索引。所有这些解决方案的索引使用十六进制值中的无符号字符串首先写入1个字节的内存。

现在,您的功能是检查这256种可能解决方案的排列,这可以通过一个简单的&amp; amp; while在线性过程中循环只是通过执行基本的碰撞检测过程检查每个单元格以查看它是否受到攻击,如果其中任何一个失败,我们可以打破循环的迭代并丢弃此解决方案,然后转到for循环的下一次迭代并继续该过程。如果解决方案的容器确实满足它,那么您希望标记该解决方案的出现并将其保存到另一个容器中,该容器包含循环的每次迭代的所有有效解决方案。这也可以消除递归的需要或用途。

我知道这很长,但需要花费很多时间来详细解释它,我花了几个小时来检查问题,绘制图形,编写小程序并解释我做了什么以及为什么我做到了,所以请随时发表评论,让我知道你对这种方法的看法。

答案 3 :(得分:2)

首先,我定义了我的基本概念攻击能力。 攻击能力是指有多少骑士可以攻击给定的细胞。

例: 角部细胞只能受到两个骑士的攻击,所以攻击能力是两个。中间细胞的攻击能力是8。

细胞的攻击能力

| 2 | 3 | 4 | 4 | 4 | 4 | 3 | 2 |

| 3 | 4 | 6 | 6 | 6 | 6 | 4 | 3 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 3 | 4 | 6 | 6 | 6 | 6 | 4 | 3 |

| 2 | 3 | 4 | 4 | 4 | 4 | 3 | 2 |

计算攻击性

AttackingNodes::AttackingNodes(int x, int y)
{
    target = new Element(x, y);
    CreateAtackList();
}

void AttackingNodes::CreateAtackList()
{
    for(int diffx = -2; diffx <=2; ++diffx)
    {

        for(int diffy = -2; diffy <=2; ++diffy)
        {
            if((diffx*diffx + diffy* diffy) == 5)
            {
                AddAttack(target->_X + diffx, target->_Y + diffy);
            }
        }
    }
}

void AttackingNodes::AddAttack( int x, int y )
{
    if(x >= 0 && y >= 0 && x < BOARD_SIZE && y < BOARD_SIZE)
    {
        Element* element = new Element(x, y);
        attackers.push_back(element);
    }
}

攻击节点中攻击者的大小等于攻击性。

然后multimap创建针对攻击节点的攻击性

for(int x = 0; x < BOARD_SIZE; ++x)
{
    for(int y = 0; y < BOARD_SIZE; ++y)
    {
        AttackingNodes* nodes = new AttackingNodes(x, y);
        attackNodes[x][y] = nodes;
        mapAttackPriority.insert(std::make_pair(nodes->attackers.size(), nodes));
    }
}

如果攻击性较低,则攻击给定单元的选项较少。

因此选择了multimap中的第一个节点,它具有较少的攻击选项。

第一个单元格为0,0。 攻击0,0

有两种选择
1, 2 or 2, 1

如果它们为空,则选择1,2和攻击单元格。它可以攻击6个细胞。

攻击(..)将骑士放置在给定的单元格中。 Atackers和目标是相同的方式。因此,这里使用计算攻击能力时生成的数据。

bool Solution::attack( Element* nodes )
{
    ++knightCount;
    AttackingNodes* attackList = PriorityTargets::inst->attackNodes[nodes->_X][nodes->_Y];
    std::list<Element*>::iterator itr;

    board[nodes->_X][nodes->_Y] = CC_KNIGHT;

    for(itr = attackList->attackers.begin(); itr != attackList->attackers.end(); ++itr)
    {
        Element* attackNode = *itr;

        if(board[attackNode->_X][attackNode->_Y] == CC_EMPTY)
        {
            board[attackNode->_X][attackNode->_Y] = CC_ATTACKED;
        }
    }

    return false;
}

| A | 0 | A | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | A | 0 | 0 | 0 | 0 |

| 0 | K | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | A | 0 | 0 | 0 | 0 |

| A | 0 | A | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

然后算法搜索下一个空单元格(没有 K 夜晚,没有 A ttacked),攻击性最低,可用选项攻击它。

AttackingNodes* PriorityTargets::GetNextNode( Solution* solution )
{

    std::multimap<size_t, AttackingNodes*>::iterator priorityItr;
    for(priorityItr  = mapAttackPriority.begin(); priorityItr != mapAttackPriority.end(); ++priorityItr)
    {
        AttackingNodes* attackNodes = priorityItr->second;
        if(solution->board[attackNodes->target->_X][attackNodes->target->_Y] == CC_EMPTY)
        {
            return attackNodes;
        }
    }

    return NULL;
}

它将成为第二个选项的另一个角节点,它将持续到骑士计数大于12或没有空单元格。

骑士计数大于12,它是失败尝试并且支持跟踪。 如果没有空单元格那么它就是一个解决方案。

Solution::Solution()
{
    Clear();
}

void Solution::Print()
{

    std::cout << std::endl ;
    for(int x = 0; x < BOARD_SIZE; ++x)
    {
        for(int y = 0; y < BOARD_SIZE; ++y)
        {
            std::cout << (int)board[x][y] << " ";
        }
        std::cout << std::endl ;
    }


    std::cout << std::endl ;

}

bool Solution::Solve( Solution* solution )
{
    AttackingNodes* nextAttackingNode = PriorityTargets::inst->GetNextNode(solution);

    if(nextAttackingNode != NULL)
    {
        Solution* newSolutioon = new Solution();
        std::list<Element*>::iterator itr;
        for(itr = nextAttackingNode->attackers.begin(); itr != nextAttackingNode->attackers.end(); ++itr)
        {
            Element* attack = *itr;


                *newSolutioon = *solution;
                newSolutioon->attack(attack);
                if(newSolutioon->knightCount < 13)
                {
                    Solve(newSolutioon);
                }
                else
                {
                    //Fail
                    newSolutioon->Clear();
                }
        }

        delete newSolutioon;
    }
    else
    {
        std::cout << "Solved" << std::endl;
        solution->Print();
    }

    return false;
}


void Solution::Clear()
{
    memset(board, 0, BOARD_SIZE*BOARD_SIZE);
    knightCount = 0;
}

我在visual studio 2008发布模式下得到的回答时间不到500毫秒。 我使用2代表骑士,1代表攻击。

enter image description here

答案 4 :(得分:0)

为各种板尺寸限制骑士统治问题已经做了很多工作。 Here是一篇文章,似乎总结了之前的所有工作,并添加了一些新的曲目。 here是一篇声称展示骑士统治的线性时间算法的文章。我甚至找到了对constant-time knights domination algorithm的引用,但我不知道有人在哪里煞费苦心地写出来。首先参与大脑,然后再编写代码。