八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
N皇后问题是由8皇后拓展而来,其本质还是8皇后问题,主要思路是通过不断递归摆放棋子来减少可以摆放棋子的宫格,最终找到解。
先从简单的 4*4 宫格来讨论:
初始化 4*4 棋盘,0表示没有棋子,1表示放置了棋子,小于0表示不能放置棋子(路障)
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
从第一行第一个开始遍历,放置一个棋子然后在横竖斜对角放置路障
1 -1 -1 -1
-1 -1 0 0
-1 0 -1 0
-1 0 0 -1
接着递归进入第二行,发现有为0的的宫格表示可以放置棋子,继续放置棋子
1 -1 -1 -1
-1 -1 1 -1
-1 -1 -1 -1
-1 0 -1 -1
此时继续递归进入第三行,发现已经不能放置棋子了,接着返回第二行重新放置棋子。
1 -1 -1 -1
-1 -1 -1 1
-1 0 -1 -1
-1 -1 0 -1
此时继续递归进入第三行放置棋子,直到最后一行可以放置棋子则表示这种布局有解。
实现细节:
1.因为每次都是按行向下递归,所以不用在棋子所在行和上方放置路障
2.如果想恢复上一次递归的状态不能直接设置值,例如:
1 -1 -1
-1 -1 1
-1 -1 -1
此时已经递归到第二行,第三行不可放置棋子如果恢复到第二行放置之前的状态,将第二行的棋子和路障去掉会变成:
1 -1 -1
-1 -1 0
-1 0 0
这时我们同时把第一行棋子的路障去掉了,所以不正确,解决办法是用增量而不是固定值,每设置一次棋子都+1,路障对应的位置都-1:
1 -1 -1
-1 -1 1
-1 -1 -2
此时还原的时候棋子-1,路障+1即可:
1 -1 -1
-1 -1 0
-1 0 -1
GO语言实现
package main
import (
"fmt"
)
const N int = 8
var Board [N][N]int = [N][N]int{{}} //初始化棋盘
var Result int = 0
func setBlock(row int, col int, val int) {
//设置不可放置宫格
for r, c := row, col; r < N && c >= 0; {
//设置左下宫格
Board[r][c] += val
c--
r++
}
for r := row; r < N; r++ {
//设置正下方宫格
Board[r][col] += val
}
for r, c := row, col; r < N && c < N; {
//设置右下方宫格
Board[r][c] += val
c++
r++
}
}
func recursive(row int, col int) {
if row == N {
Result++
return
}
for ; col < N; col++ {
if Board[row][col] == 0 {
//设置不可访问宫格
setBlock(row, col, -1)
recursive(row+1, 0)
//还原上一次设置
setBlock(row, col, 1)
}
}
}
func main() {
recursive(0, 0)
fmt.Println(Result)
}
N | 解的个数 |
---|---|
1 | 1 |
2 | 0 |
3 | 0 |
4 | 2 |
5 | 10 |
6 | 4 |
7 | 40 |
8 | 92 |
9 | 352 |
10 | 724 |
11 | 2680 |
12 | 14200 |
13 | 73712 |
14 | 365596 |
15 | 2279184 |
16 | 14772512 |
17 | 95815104 |
18 | 666090624 |
19 | 4968057848 |
20 | 39029188884 |