八皇后问题。19世纪著名的数学家高斯在1850年曾提出过这样的问题:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。其后的近一个世纪的时间里,许多人一直在求解这个问 题。计算机发明后,这个问题采用深度优先搜索(DFS)算法容易求解。
解决方案:
-
任意两个皇后在不在同一列或同一斜线上,可以用三个一维数组a、b和c判断。行标记为i,列标记为j,a[j]为0表示第j列没有放置皇后,为1表示放置皇后。b[i-j+N-1]为0表示标记为(i-j+N-1)的主对角线没有被皇后占用,为1表示被占用。c[i+j]为0表示标记为(i+j)的副对角线没有被皇后占用,为1表示被占用。即列标记、主对角线标记和副对角线标记分别作为三个数组的索引,记录其有没有被占用,简化了程序,节省了开销。用一个二维数组d[i][j]为0表示该位置没有放置皇后,为1表示放置皇后。
-
先摆放i=0行,在第0列摆放皇后,把d[0][0]标记为1,然后把皇后所占用的列、主对角线和副对角线标记为1。递归摆放i=1行,判断条件a[j]、b[i-j+N-1]和c[i+j]均为0是否成立,若成立,在该位置摆放皇后,把该位置、皇后所占用的列、主对角线和副对角线标记为1。继续在下一行用同样的方法寻找合适的位置摆放皇后,当i=N时,得到一种摆放方法。递归返回,回到上一行,把第i-1行摆放的皇后撤掉,把相应的标记请0,在该行的其它列寻找合适的摆放位置,然后把相应的标记置1,继续在下一行寻找合适的位置;若在第i-1行其它位置没有找到合适位置,回到第i-2行,重复第i-1行的操作,这样,若有新的摆放方法,就能找到。所有的位置都尝试完后,程序结束。
cpp
#include <stdio.h>
#include <stdlib.h>
#define N 6 // 皇后数量
int board[N][N]; // 二维棋盘:0表示空,1表示有皇后
int cols[N]; // 列标记:cols[i]=1 表示第i列已被占用
/* 主对角线(\)标记为索引row - col + N - 1 */
int diag1[2 * N - 1];
/* 副对角线(/)标记为索引row + col */
int diag2[2 * N - 1];
int solution_count = 0; // 解的计数器
void sitequeen(int row);
void chart_solution();
void coords_solution();
void print_diag12();
int main()
{
printf("开始求解 %d 皇后问题...\n\n", N);
sitequeen(0); // 从第 0 行开始
printf("总共找到 %d 种解法。\n", solution_count);
if(solution_count != 0)
print_diag12( );
return 0;
}
/* 回溯递归函数, 当前正在处理的行号row为参数 */
void sitequeen(int row) {
// 1. 终止条件:如果行号等于 N,说明 0~N-1 行都成功放置了皇后
if (row == N) {
chart_solution();
coords_solution();
return;
}
// 2. 尝试当前行的每一列 (0 到 N-1)
for (int col = 0; col < N; col++) {
// 3. 计算对角线索引
int d1_index = row - col + N - 1; // 主对角线索引 (防止负数,加N-1)
int d2_index = row + col; // 副对角线索引
// 4. 检查是否安全 (利用标记数组 O(1) 判断)
// 如果列、主对角线、副对角线都没有被占用
if (cols[col] == 0 && diag1[d1_index] == 0 && diag2[d2_index] == 0)
{
// --- 做选择 ---
board[row][col] = 1; // 棋盘上放皇后
cols[col] = 1; // 标记列被占用
diag1[d1_index] = 1; // 标记主对角线被占用
diag2[d2_index] = 1; // 标记副对角线被占用
sitequeen(row + 1); // 递归去处理下一行
/* 回溯, 以便尝试下一列 */
board[row][col] = 0;
cols[col] = 0;
diag1[d1_index] = 0;
diag2[d2_index] = 0;
}
}
}
// 打印当前找到的图形解
void chart_solution() {
int i, j;
solution_count++;
printf("摆法 %d:\n", solution_count);
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++)
{
if (board[i][j] == 1)
{
printf(" Q ");
}
else
{
printf(" . ");
}
}
printf("\n");
}
printf("\n");
}
// 打印当前找到的坐标解
void coords_solution()
{
int i, j;
printf("摆放位置:\n");
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (board[i][j] == 1)
printf("(%d, %d): (%d, %d)\n", i, j,
i - j + N - 1, i + j);
printf("\n");
}
// 打印对角线数组的值
void print_diag12( )
{
int i;
printf("主对角线数组diag1的元素: ");
for(i = 0; i< 2 * N - 1; i++)
printf("%d ", diag1[i]);
printf("\n");
printf("副对角线数组diag2的元素: ");
for(i = 0; i< 2 * N - 1; i++)
printf("%d ", diag2[i]);
printf("\n");
}
开始求解 6 皇后问题...
摆法 1:
. Q . . . .
. . . Q . .
. . . . . Q
Q . . . . .
. . Q . . .
. . . . Q .
摆放位置:
(0, 1): (4, 1)
(1, 3): (3, 4)
(2, 5): (2, 7)
(3, 0): (8, 3)
(4, 2): (7, 6)
(5, 4): (6, 9)
|------|----|----|---------|---------|
| 皇后编号 | 放置位置 ||||
| 皇后编号 | 行号 | 列号 | 主对角线 编号 | 副对角线 编号 |
| 1 | 0 | 1 | 4 | 1 |
| 2 | 1 | 3 | 3 | 4 |
| 3 | 2 | 5 | 2 | 7 |
| 4 | 3 | 0 | 8 | 3 |
| 5 | 4 | 2 | 7 | 6 |
| 6 | 5 | 4 | 6 | 9 |
[表1 皇后摆放位置]
由摆放位置数据构造表1,6个皇后的行号、列号、主对角线编号和副对角线标号都不同,满足要求。由下面三种摆法的位置数据也容易得出每一种摆法都满足要求。
摆法 2:
. . Q . . .
. . . . . Q
. Q . . . .
. . . . Q .
Q . . . . .
. . . Q . .
摆放位置:
(0, 2): (3, 2)
(1, 5): (1, 6)
(2, 1): (6, 3)
(3, 4): (4, 7)
(4, 0): (9, 4)
(5, 3): (7, 8)
摆法 3:
. . . Q . .
Q . . . . .
. . . . Q .
. Q . . . .
. . . . . Q
. . Q . . .
摆放位置:
(0, 3): (2, 3)
(1, 0): (6, 1)
(2, 4): (3, 6)
(3, 1): (7, 4)
(4, 5): (4, 9)
(5, 2): (8, 7)
摆法 4:
. . . . Q .
. . Q . . .
Q . . . . .
. . . . . Q
. . . Q . .
. Q . . . .
摆放位置:
(0, 4): (1, 4)
(1, 2): (4, 3)
(2, 0): (7, 2)
(3, 5): (3, 8)
(4, 3): (6, 7)
(5, 1): (9, 6)
总共找到 4 种解法。
主对角线数组diag1的元素: 0 0 0 0 0 0 0 0 0 0 0
副对角线数组diag2的元素: 0 0 0 0 0 0 0 0 0 0 0
找到所有可能的放置位置后,还要继续尝试,但都失败了,所以两个对角线数组元素都变为了0。