回溯算法求解N皇后问题(Go语言)
介绍
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。请问有多少种满足条件的情况?
当 n = 4 时,有以下两种情况:
回溯算法
N皇后的传统解法是暴力递归,在我看来暴力递归的精髓就是尝试,N皇后问题要求任意两个皇后都不共行,不共列,不共斜线。因此在一个N*N的棋盘上放N个皇后,因此可以看成由上到下每一行放一个皇后的尝试模型。
func problemFunction(nums []type) [][]type {
ans := [][]type{}
temp := []type{}
n := len(nums)
var backTrack func(tmp []type) // 回溯函数声明
backTrack = func(tmp []type) {
if len(tmp) == n { // 递归出口
tmpcp := make([]type, n)
copy(tmpcp, tmp)
ans = append(ans, tmpcp)
// ans = append(ans,append([]int(nil),temp...))
}
for i := 0; i < n; i++ {
// 条件筛选
temp = append(temp, nums[i])
backTrack(temp)
temp = temp[0 : len(temp)-1]
}
}
backTrack(temp)
return ans
}
动画演示
4皇后回溯
经过参考其他教程资料后,发现判断是否可以相互攻击通常不是直接向八个方向进行遍历,而是通过棋盘标记法进行判断。即每个皇后对其可以攻击到的棋盘位置进行+1,回溯撤回后-1。但是想不到的话直接每次都遍历其其可以攻击到的位置上是否有其它皇后也没问题。
代码
51. N 皇后 困难
// N皇后问题
func solveNQueens(n int) [][]string {
ans := make([][]string, 0)
// 生成nxn棋盘
gen := func(n int) []string {
ans := make([]string, n)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
ans[i] += "."
}
}
return ans
}
temp := gen(n) // 生成棋盘
// 判断当前位置放置皇后是否相互攻击
isValid := func(r, c int) bool {
// 纵向
for i := 0; i < n; i++ {
if temp[i][c] == 'Q' {
return false
}
}
// 横向
for i := 0; i < n; i++ {
if temp[r][i] == 'Q' {
return false
}
}
// 右下
for i := 0; r+i < n && c+i < n; i++ {
if temp[r+i][c+i] == 'Q' {
return false
}
}
// 右上
for i := 0; r-i >= 0 && c-i >= 0; i++ {
if temp[r-i][c-i] == 'Q' {
return false
}
}
// 左下
for i := 0; r+i < n && c-i >= 0; i++ {
if temp[r+i][c-i] == 'Q' {
return false
}
}
// 左上
for i := 0; r-i >= 0 && c+i < n; i++ {
if temp[r-i][c+i] == 'Q' {
return false
}
}
return true
}
// 放置皇后
put := func(r, c int, role byte) {
line := []byte(temp[r])
line[c] = role
temp[r] = string(line)
}
var backTrack func(r int) // 回溯函数声明
backTrack = func(r int) {
if r == n {
tempcp := make([]string, n)
copy(tempcp, temp)
ans = append(ans, tempcp)
return
}
for j := 0; j < n; j++ {
if !isValid(r, j) {
continue
}
// 做出选择
put(r, j, 'Q')
// 回溯
backTrack(r + 1)
// 撤销选择
put(r, j, '.')
}
}
// 执行回溯函数
backTrack(0)
return ans
}
52. N皇后 II 困难
func totalNQueens(n int) int {
return len(solveNQueens(n))
}
// 生成nxn棋盘
func gen(n int) []string {
ans := make([]string, n)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
ans[i] += "."
}
}
return ans
}
// 判断当前位置放置皇后是否相互攻击
func isValid(board []string, r, c, n int) bool {
// 纵向
for i := 0; i < n; i++ {
if board[i][c] == 'Q' {
return false
}
}
// 横向
for i := 0; i < n; i++ {
if board[r][i] == 'Q' {
return false
}
}
// 右下
for i := 0; r+i < n && c+i < n; i++ {
if board[r+i][c+i] == 'Q' {
return false
}
}
// 右上
for i := 0; r-i >= 0 && c-i >= 0; i++ {
if board[r-i][c-i] == 'Q' {
return false
}
}
// 左下
for i := 0; r+i < n && c-i >= 0; i++ {
if board[r+i][c-i] == 'Q' {
return false
}
}
// 左上
for i := 0; r-i >= 0 && c+i < n; i++ {
if board[r-i][c+i] == 'Q' {
return false
}
}
return true
}
// 放置皇后
func put(board []string, r, c int, role byte) {
line := []byte(board[r])
line[c] = role
board[r] = string(line)
}
// N皇后问题
func solveNQueens(n int) [][]string {
ans := make([][]string, 0)
temp := gen(n) // 生成棋盘
var backTrack func(r int) // 回溯函数声明
backTrack = func(r int) {
if r == n {
tempcp := make([]string, n)
copy(tempcp, temp)
ans = append(ans, tempcp)
return
}
for j := 0; j < n; j++ {
if !isValid(temp, r, j, n) {
continue
}
// 做出选择
put(temp, r, j, 'Q')
// 回溯
backTrack(r + 1)
// 撤销选择
put(temp, r, j, '.')
}
}
// 执行回溯函数
backTrack(0)
return ans
}
时间复杂度: O ( N ! ) O(N!) O(N!)