八皇后问题详细推导(递归和非递归,Go语言实现)
WAPWO · · 4205 次点击 · · 开始浏览回溯法解八皇后问题(递归和非递归,Go语言实现)
问题重现:
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?如果问题拓展到n*n,又有多少种摆法?
分析:
回溯法解8*8问题
棋盘横坐标i,纵坐标j,7>=i>=0且7>=j>=0,皇后摆法位置(i,j)
用pos[i]记录第i个皇后放的位置
分析:用0表示没有,用1表示已有
用a[i]表示第i行是否摆放了皇后(a取值在0~7)
用b[j]表示第j列是否摆法了皇后(b取值在0~7)
用c[j-i+7]正对角线是否摆放了皇后(c取值在0~14)
用d[i+j]斜对角线是否摆法了皇后(d取值在0~14)
优化:
a[i]这个判断条件可以省略,因为在取皇后的时候,皇后不能放在同一行,于是可以假定第i个皇后放到第j列,这里的第i个皇后的i就是所在行。
第i个皇后在第j列
for(j=0;j<8;j++){
if(i>7){
count++
打印当前解集
}
if( (i,j)位置对应abcd均表示为0 ){
置对应abcd为1
递归调用下一个
置对应abcd为0
}
}
非递归版本1
package main
import (
"fmt"
)
var pos, b [80]int
var c, d [150]int
/*置相应数组为n{0|1}*/
func putn(i, j, flag int) {
b[j], c[j-i+7], d[i+j] = flag, flag, flag
}
/*
*pos[i]=j表示第i行的皇后在第j列
*b[i]表示第i行是否放置了皇后,也就限定了每行只可能出现一个皇后,也就有每行都一定有且只有一个皇后
*c[j-i+7]表示主对角线是否放置了皇后
*d[i+j]表示次对角线是否放置了皇后
*0表示没有,1表示放置了皇后
*检查相应位置是否可以放置皇后,冲突时返回1
*/
func checkPos(i, j int) int {
if b[j] == 1 || c[j-i+7] == 1 || d[i+j] == 1 {
return 1
}
return 0
}
/*
*回溯非递归写法:八皇后问题 作者:天之
*/
func backTrackQueen(n int) int {
//传进来的n值为原值的(n-1)
count := 0 //统计摆法
i := 0 //已经放置了多少个皇后,也就是处理完了多少行
conflict := 0 //是否冲突,默认0没有冲突
for {
if conflict == 0 {
//没有冲突
if i == n {
//搜索到一个解
count++
//开始回溯,重新开始
for pos[i] == n {
//特别注明1:i=7,pos[7]=n时,i=7无需释放(见注明2),并且只要i=7时才会执行这句
//如果颠倒位置,将会使得i=6时原位置不被释放,故而必须先i--,再释放
i--
/*if i == -1 {
//结束条件
return count
}*/
//释放空间,使得位置可以放
putn(i, pos[i], 0)
}
pos[i]++
} else {
//else if i < n,因为i是步增的,所以else 后的if i<n可以不要
//打上新标记,注明不可放
//特别指出2:i=7时并没有执行这条语句,因为在i=7时,只要找到合适位置就可以了,接着就是回溯了
putn(i, pos[i], 1)
//进入下一行第0列搜索
i++
pos[i] = 0
//直到i=7,已知试探
}
} else {
//发现冲突,并且搜索从0深度到了n
for pos[i] == n {
//特别注明3:这里和上面的还是有区别的,因为第i行根本还处于冲突中,所以必须先i--,再释放,
i--
if i == -1 {
//真实的结束出口,而且是必然的
return count
}
//释放空间,使得位置可以放
putn(i, pos[i], 0)
}
pos[i]++
}
conflict = checkPos(i, pos[i])
}
//return count
}
func main() {
fmt.Println(backTrackQueen(8 - 1))
}
非递归版本2
package main
import (
"fmt"
)
var col [9]int //col[j]=i,表示第j列的第i行放置了皇后
var row [9]int //row[i]=0表示第i行没有皇后
var b [17]int //0表示主对角线没有放皇后
var c [17]int //0表示次对角线没有放皇后
/*在指定位置设定新标志*/
func putn(num, n, flag int) {
row[col[num]] = flag
b[num+col[num]] = flag
c[n+num-col[num]] = flag
}
/*检查位置是否可用,可用返回0*/
func checkPos(num, n int) int {
if row[col[num]] == 1 || b[num+col[num]] == 1 || c[n+num-col[num]] == 1 {
return 1
}
return 0
}
/*
*回溯非递归写法:八皇后问题 作者:天之
*/
func backTrackQueen(n int) int {
count := 0 //统计摆法
//初始化
num := 1 //已经放置了的皇后数目
flag := 0 //0表示没有发生冲突
col[1] = 1 //第1列选第1个位置
col[0] = 0 //第0列数据不用
for num > 0 {
if flag == 0 {
//没有冲突
if num == n {
count++
//发生冲突,开始回退
for col[num] == n {
//num减到0的时候也就意味着结束了
num--
//清除之前的状态标记,释放位置
putn(num, n, 0)
}
col[num]++
} else {
//打上新的状态标记,表示不可再放
putn(num, n, 1)
//从下一列的第i个位置继续搜索
num++
col[num] = 1
}
} else {
//本列发生冲突并且搜到了n,开始回退
for col[num] == n {
num--
putn(num, n, 0)
}
//继续搜索本列下一个
col[num]++
}
flag = checkPos(num, n)
}
return count
}
func main() {
fmt.Println(backTrackQueen(8))
}
递归版本
package main
import (
"fmt"
)
//初始值默认都为0,为便于扩展,空间大小乘以了10
//pos[i]=j表示第i行皇后放在j位置
//用a[i]表示第i行是否摆放了皇后(a取值在0~7)
//用c[j-i+7]正对角线是否摆放了皇后(c取值在0~14)
//用d[i+j]斜对角线是否摆法了皇后(d取值在0~14)
var pos, b [80]int
var c, d [150]int
/*置相应数组为n{0|1}*/
func putn(i, j, n int) {
pos[i], b[j], c[j-i+7], d[i+j] = j, n, n, n
}
/*检查相应位置是否可以放置皇后*/
func checkPos(i, j int) int {
if b[j] == 1 || c[j-i+7] == 1 || d[i+j] == 1 {
return 0
}
return 1
}
/*打印皇后的位置*/
func printQueen(n int) {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if pos[i] == j {
fmt.Print(1)
} else {
fmt.Print(0)
}
}
fmt.Println()
}
fmt.Println("==================")
}
/*
*最简洁回溯法之全排列解法:八皇后问题递归核心 作者:天之
*/
func queen(i, n int, count *int) {
if i > 7 {
//说明8皇后已经放好,也就是得到了解
*count++
printQueen(n)
//避免无用的搜索,应该立即返回
return
}
for j := 0; j < n; j++ {
if checkPos(i, j) == 1 {
putn(i, j, 1)
queen(i+1, n, count)
putn(i, j, 0)
}
}
}
func main() {
n, count := 8, 0
queen(0, n, &count)
fmt.Println(count)
}
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
回溯法解八皇后问题(递归和非递归,Go语言实现)
问题重现:
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?如果问题拓展到n*n,又有多少种摆法?
分析:
回溯法解8*8问题
棋盘横坐标i,纵坐标j,7>=i>=0且7>=j>=0,皇后摆法位置(i,j)
用pos[i]记录第i个皇后放的位置
分析:用0表示没有,用1表示已有
用a[i]表示第i行是否摆放了皇后(a取值在0~7)
用b[j]表示第j列是否摆法了皇后(b取值在0~7)
用c[j-i+7]正对角线是否摆放了皇后(c取值在0~14)
用d[i+j]斜对角线是否摆法了皇后(d取值在0~14)
优化:
a[i]这个判断条件可以省略,因为在取皇后的时候,皇后不能放在同一行,于是可以假定第i个皇后放到第j列,这里的第i个皇后的i就是所在行。
第i个皇后在第j列
for(j=0;j<8;j++){
if(i>7){
count++
打印当前解集
}
if( (i,j)位置对应abcd均表示为0 ){
置对应abcd为1
递归调用下一个
置对应abcd为0
}
}
非递归版本1
package main
import (
"fmt"
)
var pos, b [80]int
var c, d [150]int
/*置相应数组为n{0|1}*/
func putn(i, j, flag int) {
b[j], c[j-i+7], d[i+j] = flag, flag, flag
}
/*
*pos[i]=j表示第i行的皇后在第j列
*b[i]表示第i行是否放置了皇后,也就限定了每行只可能出现一个皇后,也就有每行都一定有且只有一个皇后
*c[j-i+7]表示主对角线是否放置了皇后
*d[i+j]表示次对角线是否放置了皇后
*0表示没有,1表示放置了皇后
*检查相应位置是否可以放置皇后,冲突时返回1
*/
func checkPos(i, j int) int {
if b[j] == 1 || c[j-i+7] == 1 || d[i+j] == 1 {
return 1
}
return 0
}
/*
*回溯非递归写法:八皇后问题 作者:天之
*/
func backTrackQueen(n int) int {
//传进来的n值为原值的(n-1)
count := 0 //统计摆法
i := 0 //已经放置了多少个皇后,也就是处理完了多少行
conflict := 0 //是否冲突,默认0没有冲突
for {
if conflict == 0 {
//没有冲突
if i == n {
//搜索到一个解
count++
//开始回溯,重新开始
for pos[i] == n {
//特别注明1:i=7,pos[7]=n时,i=7无需释放(见注明2),并且只要i=7时才会执行这句
//如果颠倒位置,将会使得i=6时原位置不被释放,故而必须先i--,再释放
i--
/*if i == -1 {
//结束条件
return count
}*/
//释放空间,使得位置可以放
putn(i, pos[i], 0)
}
pos[i]++
} else {
//else if i < n,因为i是步增的,所以else 后的if i<n可以不要
//打上新标记,注明不可放
//特别指出2:i=7时并没有执行这条语句,因为在i=7时,只要找到合适位置就可以了,接着就是回溯了
putn(i, pos[i], 1)
//进入下一行第0列搜索
i++
pos[i] = 0
//直到i=7,已知试探
}
} else {
//发现冲突,并且搜索从0深度到了n
for pos[i] == n {
//特别注明3:这里和上面的还是有区别的,因为第i行根本还处于冲突中,所以必须先i--,再释放,
i--
if i == -1 {
//真实的结束出口,而且是必然的
return count
}
//释放空间,使得位置可以放
putn(i, pos[i], 0)
}
pos[i]++
}
conflict = checkPos(i, pos[i])
}
//return count
}
func main() {
fmt.Println(backTrackQueen(8 - 1))
}
非递归版本2
package main
import (
"fmt"
)
var col [9]int //col[j]=i,表示第j列的第i行放置了皇后
var row [9]int //row[i]=0表示第i行没有皇后
var b [17]int //0表示主对角线没有放皇后
var c [17]int //0表示次对角线没有放皇后
/*在指定位置设定新标志*/
func putn(num, n, flag int) {
row[col[num]] = flag
b[num+col[num]] = flag
c[n+num-col[num]] = flag
}
/*检查位置是否可用,可用返回0*/
func checkPos(num, n int) int {
if row[col[num]] == 1 || b[num+col[num]] == 1 || c[n+num-col[num]] == 1 {
return 1
}
return 0
}
/*
*回溯非递归写法:八皇后问题 作者:天之
*/
func backTrackQueen(n int) int {
count := 0 //统计摆法
//初始化
num := 1 //已经放置了的皇后数目
flag := 0 //0表示没有发生冲突
col[1] = 1 //第1列选第1个位置
col[0] = 0 //第0列数据不用
for num > 0 {
if flag == 0 {
//没有冲突
if num == n {
count++
//发生冲突,开始回退
for col[num] == n {
//num减到0的时候也就意味着结束了
num--
//清除之前的状态标记,释放位置
putn(num, n, 0)
}
col[num]++
} else {
//打上新的状态标记,表示不可再放
putn(num, n, 1)
//从下一列的第i个位置继续搜索
num++
col[num] = 1
}
} else {
//本列发生冲突并且搜到了n,开始回退
for col[num] == n {
num--
putn(num, n, 0)
}
//继续搜索本列下一个
col[num]++
}
flag = checkPos(num, n)
}
return count
}
func main() {
fmt.Println(backTrackQueen(8))
}
递归版本
package main
import (
"fmt"
)
//初始值默认都为0,为便于扩展,空间大小乘以了10
//pos[i]=j表示第i行皇后放在j位置
//用a[i]表示第i行是否摆放了皇后(a取值在0~7)
//用c[j-i+7]正对角线是否摆放了皇后(c取值在0~14)
//用d[i+j]斜对角线是否摆法了皇后(d取值在0~14)
var pos, b [80]int
var c, d [150]int
/*置相应数组为n{0|1}*/
func putn(i, j, n int) {
pos[i], b[j], c[j-i+7], d[i+j] = j, n, n, n
}
/*检查相应位置是否可以放置皇后*/
func checkPos(i, j int) int {
if b[j] == 1 || c[j-i+7] == 1 || d[i+j] == 1 {
return 0
}
return 1
}
/*打印皇后的位置*/
func printQueen(n int) {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if pos[i] == j {
fmt.Print(1)
} else {
fmt.Print(0)
}
}
fmt.Println()
}
fmt.Println("==================")
}
/*
*最简洁回溯法之全排列解法:八皇后问题递归核心 作者:天之
*/
func queen(i, n int, count *int) {
if i > 7 {
//说明8皇后已经放好,也就是得到了解
*count++
printQueen(n)
//避免无用的搜索,应该立即返回
return
}
for j := 0; j < n; j++ {
if checkPos(i, j) == 1 {
putn(i, j, 1)
queen(i+1, n, count)
putn(i, j, 0)
}
}
}
func main() {
n, count := 8, 0
queen(0, n, &count)
fmt.Println(count)
}