八皇后问题
2015-06-25 10:16
369 查看
问题抛出:在棋盘上放置8个皇后,使得它们互不攻击,此时每个皇后的攻击范围为同行同列和同对角线,要求找出所有解。如图所示:
【分析】
最简单的方法是把问题转化为“从64个格子中选一个子集”,使得“子集中恰好有8个格子,且任意两个选出的格子都不在同一行、同一列、或同一个对角线上”。这正是子集枚举问题。然而,64个格子的子集有2^64个,数量级有10^19,太大了,这并不是一个很好的模型。
简化:把问题转化为“从64个格子中选8个格子”,这是组合生成问题。根据组合数学,有8C64 = 4.426*10^9种方案,少很多了,很还不够少。
再简化:观察规律,恰好每行每列各放置一个皇后。如果用C[x]表示第x行皇后的列编号,则问题变成了全排列生成问题。而0~7的排列一共只有8!=40320个,枚举量不会超过它。
注意:当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一级递归调用,这种现象成为回溯。正是因为这个原因,递归枚举算法常被成为回溯(backtracking)法,应用十分普遍。
demo 1
注意:既然是逐行放置的,则皇后肯定不会横向攻击,因此只需检查是否纵向和斜向攻击即可。
结点数现在很难减少了,但程序效率可以继续提高;利用二维数组vis[2][]直接判断当前尝试的皇后所在的列和两个对角线是否已有其他皇后。注意到主对角线标识y-x可能为负数,存取时要加上n。
demo 2
demo 2程序有个及其关键的地方:vis数组的使用,vis数组的确切含义:它表示已经放置的皇后占据了哪些列、主对角线和副对角线。将来放置的皇后不应该修改这些值。一般地,如果在回溯法中修改了辅助的全局变量,则一定要及时把它们恢复原状(除非故意保留所做修改)。另外,在调用之前一定要把vis数组清空。
注意:如果在回溯法中使用了辅助的全局变量,则一定要及时把它们恢复原状。特别地,若函数有多个出口,则需要在每个出口处恢复被修改的值。
【分析】
最简单的方法是把问题转化为“从64个格子中选一个子集”,使得“子集中恰好有8个格子,且任意两个选出的格子都不在同一行、同一列、或同一个对角线上”。这正是子集枚举问题。然而,64个格子的子集有2^64个,数量级有10^19,太大了,这并不是一个很好的模型。
简化:把问题转化为“从64个格子中选8个格子”,这是组合生成问题。根据组合数学,有8C64 = 4.426*10^9种方案,少很多了,很还不够少。
再简化:观察规律,恰好每行每列各放置一个皇后。如果用C[x]表示第x行皇后的列编号,则问题变成了全排列生成问题。而0~7的排列一共只有8!=40320个,枚举量不会超过它。
注意:当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一级递归调用,这种现象成为回溯。正是因为这个原因,递归枚举算法常被成为回溯(backtracking)法,应用十分普遍。
demo 1
#include <iostream> #include <cstdio> using namespace std; int n, tot; int C[8]; void search(int cur) { if (cur == n) { // 递归边界。只要走到这里,所有皇后必然不冲突 tot++; } else for (int i = 0; i < n; i++) { bool ok = true; C[cur] = i; // 尝试把第cur行的皇后放在第i列 for (int j = 0; j < cur; j++) { // 检查是否和前面你的皇后冲突 if (C[cur] == C[j] || cur - C[cur] == j - C[j] || // 主对角线判定 cur + C[cur] == j + C[j]) { // 副对角线判定 ok = false; break; } } if (ok) { search(cur + 1); } } } int main() { cin >> n; tot = 0; search(0); cout << tot << endl; return 0; }
注意:既然是逐行放置的,则皇后肯定不会横向攻击,因此只需检查是否纵向和斜向攻击即可。
结点数现在很难减少了,但程序效率可以继续提高;利用二维数组vis[2][]直接判断当前尝试的皇后所在的列和两个对角线是否已有其他皇后。注意到主对角线标识y-x可能为负数,存取时要加上n。
demo 2
#include <iostream> #include <cstdio> using namespace std; int n, tot; int C[8]; int vis[3][15]; void search(int cur) { if (cur == n) { tot++; } else for (int i = 0; i < n; i++) { if (!vis[0][i] && !vis[1][cur + i] && !vis[2][cur - i + n]) { C[cur] = i; vis[0][i] = vis[1][cur + i] = vis[2][cur - i + n] = 1; // 修改全局变量 search(cur + 1); vis[0][i] = vis[1][cur + i] = vis[2][cur - i + n] = 0; // 记得改回来 } } } int main() { cin >> n; tot = 0; memset(vis, 0, sizeof(vis)); search(0); cout << tot << endl; return 0; }
demo 2程序有个及其关键的地方:vis数组的使用,vis数组的确切含义:它表示已经放置的皇后占据了哪些列、主对角线和副对角线。将来放置的皇后不应该修改这些值。一般地,如果在回溯法中修改了辅助的全局变量,则一定要及时把它们恢复原状(除非故意保留所做修改)。另外,在调用之前一定要把vis数组清空。
注意:如果在回溯法中使用了辅助的全局变量,则一定要及时把它们恢复原状。特别地,若函数有多个出口,则需要在每个出口处恢复被修改的值。
相关文章推荐
- A brief introduction to C++ and Interfacing with Excel
- Eclipse Tomcat配置 一
- hdu 3549
- How to pass string parameters to an TADOQuery?
- 异常处理语句
- delphi日期格式化免操作系统依赖单元
- BZOJ4060 : [Cerc2012]Word equations
- 《Java程序设计》第16周周四:GUI编程及文件对话框的使用 项目1
- android DefaultHttpClient设置setCookieStore
- JAVA设计模式(21):行为型-迭代器模式(Iterator)
- Merge Intervals
- VB获取文件大小的方法
- Ajax中解析Json的两种方法对比分析
- Memcache教程
- C++实现MVC模式
- 引用和指针的比较
- Myeclipse将控制台日志输出到文件中和显示更多的日志信息
- [工具]Visual Studio
- FATAL: Module iptable_nat not found解决办法
- 高效产生不重复的数