问题抛出:在棋盘上放置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
#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; }
注意:如果在回溯法中使用了辅助的全局变量,则一定要及时把它们恢复原状。特别地,若函数有多个出口,则需要在每个出口处恢复被修改的值。
原文地址:http://blog.csdn.net/zyq522376829/article/details/46633265