皇后摆放问题优化求解法

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

解决方案:

  1. 任意两个皇后在不在同一列或同一斜线上,可以用三个一维数组a、b和c判断。行标记为i,列标记为j,aj为0表示第j列没有放置皇后,为1表示放置皇后。bi-j+N-1为0表示标记为(i-j+N-1)的主对角线没有被皇后占用,为1表示被占用。ci+j为0表示标记为(i+j)的副对角线没有被皇后占用,为1表示被占用。即列标记、主对角线标记和副对角线标记分别作为三个数组的索引,记录其有没有被占用,简化了程序,节省了开销。用一个二维数组dij为0表示该位置没有放置皇后,为1表示放置皇后。

  2. 先摆放i=0行,在第0列摆放皇后,把d00标记为1,然后把皇后所占用的列、主对角线和副对角线标记为1。递归摆放i=1行,判断条件aj、bi-j+N-1和ci+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 小时前
液晶段码(米/日字格)识别—倾斜校正
opencv·算法·仿射变换
luj_17682 小时前
残熵算法:风险缓冲与效率优化的融合
c语言·开发语言·网络·经验分享·算法
oddsand12 小时前
pgvector 三大相似度算法
人工智能·算法·机器学习
运筹vivo@2 小时前
LeetCode 2574. 左右元素和的差值
算法·leetcode·职场和发展·每日一题
计算机安禾3 小时前
【数据库系统原理】第4篇:关系数据结构的形式化定义:域、笛卡尔积与关系模式
数据结构·数据库·算法
手写码匠3 小时前
手写 DeepSeek 推理引擎优化:从 FP16 到 INT4 的量化加速实战
人工智能·深度学习·算法·aigc
GuWenyue3 小时前
LeetCode 76 最小覆盖子串|JS 滑动窗口标准解法
前端·算法·面试
一只齐刘海的猫3 小时前
【Leetcode】移动零
算法·leetcode·职场和发展
落羽的落羽4 小时前
【项目】JsonRpc框架——开发实现1(细节功能、字段定义、抽象层、具象层)
linux·服务器·网络·c++·人工智能·算法·机器学习
handler014 小时前
【算法】并查集(普通/扩展/带权)模板与例题
数据结构·c++·笔记·算法·c·图论·查并集