皇后摆放问题优化求解法

八皇后问题。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。

相关推荐
水蓝烟雨7 小时前
2359. 找到离给定两个节点最近的节点
算法·leetcode
澈2078 小时前
哈希表:O(1)查找的终极指南
算法·哈希算法·散列表
幻奏岚音8 小时前
AI模型用户画像分析_new
人工智能·算法·计算机视觉·数据挖掘
阿Y加油吧8 小时前
二刷 LeetCode:爬楼梯与杨辉三角,Java 实现复盘
java·算法·leetcode
落羽的落羽8 小时前
【项目】C++从零实现JsonRpc框架——项目引入
linux·服务器·开发语言·c++·人工智能·算法·机器学习
凌波粒8 小时前
LeetCode--101. 对称二叉树(二叉树)
算法·leetcode·职场和发展
不知名的忻8 小时前
堆排序(Java)
java·数据结构·算法·排序算法
_深海凉_8 小时前
LeetCode热题100-二叉树的最大深度
算法·leetcode·职场和发展
智者知已应修善业8 小时前
【51单片机独立按键和定时器中断的疑惑验证】2023-11-2
c++·经验分享·笔记·算法·51单片机
折翅嘀皇虫8 小时前
【无标题】steal_work_thread_pool
服务器·前端·算法