数学家高斯研究过组合数学中的八皇后问题,在8*8的棋盘上放置8个皇后,保证她们互不攻击,请问有多少种解法。
一个可行解
高斯的答案是76,可惜算错了,这说明这个问题是很难发现规律,所以最宜使用暴力算法,除非你能超越高斯发现巧算的规律。
即使是暴力算法,也有笨的算法和聪明的算法,前者完全枚举,而后者部分枚举。
这个问题可以分成8个步骤,每个步骤在每行放入一个皇后,当然放入前要检查是否和前面的皇后发生攻击。正因为每个步骤的工作非常相似,所以使用递归枚举。如果当前步骤没有合法选择,那么会返回上一级递归调用,所以递归枚举算法也叫回溯法。正是回溯(backtrace)的存在,所以避免了完全枚举(给解答树减枝)。
编程之前需要选择合适的数据结构,怎样存储前面的皇后的位置?用一个二维数组?其实只要用一维数组就够了,因为这些皇后不可能同列。
那怎样判断皇后是否在对角线上呢?这就需要一些数学知识了。从如果是主对角线方向上,那么x-y的值会相等;如果是从对角线上,x+y的值会相等。
static int cnt = 0;
void search(int cur, int[] queens) {
if (cur == queens.length) {
System.out.println(Arrays.toString(queens));
cnt++;
return;
}
// i 表示 列
for (int i = 0; i < queens.length; i++) {
// 坐标 cur, i
// 坐标 j, queens[j]
boolean ok = true;
for (int j = 0; j < cur; j++) {
if (queens[j] == i || cur - i == j - queens[j] || cur + i == j + queens[j]) {
ok = false;
break;
}
}
if (ok) {
queens[cur] = i;
search(cur + 1, queens);
}
}
}
如果把检查的那段代码抽象成方法,那么会更加可读。
递归枚举算法有一个极易出错的点,就是如果存在全局变量,需要考虑当前的递归分支对其他递归分支的影响。