皇后摆放问题优化求解法

八皇后问题。19世纪著名的数学家高斯在1850年曾提出过这样的问题:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。其后的近一个世纪的时间里,许多人一直在求解这个问 题。计算机发明后,这个问题采用深度优先搜索(DFS)算法容易求解。

解决方案:

  1. 任意两个皇后在不在同一列或同一斜线上,可以用三个一维数组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表示放置皇后。

  2. 先摆放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。

相关推荐
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【区间贪心】:线段覆盖
c++·算法·贪心·csp·信奥赛·区间贪心·线段覆盖
痴男本疒2 小时前
从一道算法题发现的泛型问题
算法
itzixiao2 小时前
L1-054 福到了(15 分)[java][python]
java·python·算法
M--Y2 小时前
Redis集群和典型应用场景
redis·算法·哈希算法·集群
MediaTea2 小时前
AI 术语通俗词典:召回率(分类)
人工智能·算法·机器学习·分类·数据挖掘
ECT-OS-JiuHuaShan2 小时前
哲学的本质,是递归因果
java·开发语言·人工智能·科技·算法·机器学习·数学建模
_深海凉_2 小时前
LeetCode热题100-26. 删除有序数组中的重复项
python·算法·leetcode
睡觉就不困鸭2 小时前
第14天 四数之和
数据结构·算法
云泽8083 小时前
二叉树高阶笔试算法题精讲(一):序列化、层序遍历、LCA 与 BST 转换
数据结构·c++·算法