八皇后问题 - 回溯法的应用

题目

  • 大意:
    在国际象棋的棋盘上,按照国际象棋的规则,摆放8个皇后,使得不互相攻击,要求找出所有的解。皇后的攻击范围为同行同列和同对角线。

知识点

  • 回溯法

思路

  • 在解决这类问题时(是否合法/存在限制条件),我们可以在暴力求解使用递归时同时检查是否已经不满足要求,如果不满足要求则直接递归返回,减少搜索树的大小,就是我们的“回溯法”。
  • 在这道题目中,基于皇后的性质,我们必然不可能在一行中有多个皇后,因此我们每一层的遍历可以按照“逐行”或“逐列”搜索,即每一层递归确定一行或一列可以放置的皇后的位置,记录当前的状态(当然这个状态是临时的,因为该层满足条件状态不唯一)然后逐渐向下递归。若条件不满足或到达递归边界后一层一层返回到当前状态,更新下一个可能的状态。如果当前所有可能的状态更新完毕,则返回上一层(去更新可能的状态)
  • 这里的主对角线与副对角线的判断方法有一个巧妙之处,我们看到“列-行”与“列+行”可以代表的主对角线和副对角线是唯一的,因此我们可以用这样的方式去判断某一条主对角线或副对角线是否已经被放置皇后: !vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i],这里使用的vis[3][15]vis[0]代表某列,vis[1]代表某一主对角线,vis[2]代表某一副对角线。在判断时候我们的主对角先使用了cur - i + 8是因为主对角线的表示可能为负,如下图所示:

图1 - 列-行唯一标识了主对角线

代码

# include <iostream>
using namespace std;

int vis[3][15];  // 记录是否已放置
int result[8][8];  // 用于输出结果

void search(int cur) {  // cur - 递归的层数,每一层相当于在第cur行放置一个queen
    if (cur >= 8) {
        // 到达递归边界,输出一个结果(此时到达这里的,必然满足条件,不满足的在前面直接已经返回)
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++)  cout << result[i][j] << " ";
            cout << endl;
        }
        cout << endl;
        return;  // 不要忘记返回
    }

    for (int i = 0; i < 8; i++) {  // 在第cur行第i列放置chess的情况
    	// 第i列、(i-cur) 号主对角线、(cur+i) 号副对角线都没放置,则可以更新为该状态
        if (!vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i]) {  
            // 更新状态,第i列、(i-cur) 号主对角线、(cur+i) 号副对角线设置为已放置
            vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 1;  
            result[cur][i] = 1;  // 记录当前状态

            search(cur + 1);  // 向下递归

            // 递归回来还原之前的状态!!切记
            vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 0;
            result[cur][i] = 0;  // 记录当前结果
        }
    }
}

int main() {
    memset(vis, 0, sizeof(int[3][8]));  // 初始化
    search(0);
    return 0;
}

过程中遇到的问题 & 解决

  • 使用列-行标识唯一主对角线时候,注意可能出现的负数的情况
  • 回溯法一定要记得在状态递归返后,还原之前的状态,再去寻找当前递归层下一个可能的状态
  • 到达递归边界不要忘记return,不然程序会出现异常

测试

输入:

/

结果:✔️(只列举了两个)

1 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 0 0 0 0 1 0 0 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 1 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 

1 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 1 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
      ...
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值