八皇后问题说明
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年
提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法
回溯算法思路
八皇后问题如果用穷举法需要尝试88=16,777,216种情况。每一列放一个皇后,可以放在第 1 行,第 2 行,……,直到第8行。穷举的时候从所有皇后都放在第1行的方案开始,检验皇后之间是否会相互攻击。如果会,把列H的皇后挪一格,验证下一个方案。移到底了就“进位”到列G的皇后挪一格,列H的皇后重新试过全部的8行。这种方法是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。
回溯算法优于穷举法。将列A的皇后放在第一行以后,列B的皇后放在第一行已经发生冲突。这时候不必继续放列C的皇后,而是调整列B的皇后到第二行,继续冲突放第三行,不冲突了才开始进入列C。如此可依次放下列A至E的皇后。将每个皇后往右边横向、斜向攻击的点位用叉标记,发现列F的皇后无处安身。这时回溯到列E的皇后,将其位置由第4行调整为第8行,进入列F,发现皇后依然无处安身,再次回溯列E。此时列E已经枚举完所有情况,回溯至列D,将其由第2行移至第7行,再进入列E继续。按此算法流程最终找到如图3所示的解,成功在棋盘里放下了8个“和平共处”的皇后。继续找完全部的解共92个。
回溯算法求解八皇后问题的原则是:有冲突解决冲突,没有冲突往前走,无路可走往回退,走到最后是答案。为了加快有无冲突的判断速度,可以给每行和两个方向的每条对角线是否有皇后占据建立标志数组。放下一个新皇后做标志,回溯时挪动一个旧皇后清除标志。
编程实现
Haskall
queens :: Int -> [[Int]]
queens n = map reverse $ queens' n
where queens' 0 = [[]]
queens' k = [q:qs | qs <- queens' (k-1), q <- [1..n], isSafe q qs]
isSafe try qs = not (try `elem` qs || sameDiag try qs)
sameDiag try qs = any (\(colDist,q) -> abs (try - q) == colDist) $ zip [1..] qs
GO
package main
import "fmt"
func main() {
var balance = [8]int{0, 0, 0, 0, 0, 0, 0, 0}
queen(balance, 0)
}
func queen(a [8]int, cur int) {
if cur == len(a) {
fmt.Print(a)
fmt.Println()
return
}
for i := 0; i < len(a); i++ {
a[cur] = i
flag := true
for j := 0; j < cur; j++ {
ab := i - a[j]
temp := 0
if ab > 0 {
temp = ab
} else {
temp = -ab
}
if a[j] == i || temp == cur-j {
flag = false
break
}
}
if flag {
queen(a, cur+1)
}
}
}
JS
function queen(a, cur) {
if (cur==a.length) { console.log(a); return; }
for (var i = 0; i < a.length; i++) {
a[cur] = i;
var flag = true;
for (var j = 0; j < cur; j++) {
var ab = i - a[j];
if (a[j]==i||(ab>0?ab:-ab)==cur-j) { flag=false; break };
};
if (flag) { queen(a,cur+1); }
};
};
queen([1,1,1,1,1,1,1,1],0);
C++
#include <iostream>
using namespace std;
const int N = 8;
int arr[10], total_cnt;
// arr记录每一行(X)皇后的Y坐标
bool isPlaceOK(int *a, int n, int c) {
for (int i = 1; i <= n - 1; ++i) {
if (a[i] == c || a[i] - i == c - n || a[i] + i == c + n)
return false;
//检查位置是否可以放
//c是将要放置的位置
//a[i] == c如果放在同一列,false
//a[i] -+ i = c -+ n 如果在对角线上,false
}
return true;
}
void printSol(int *a) {
for (int i = 1; i <= N; ++i) { //遍历每一行
for (int j = 1; j <= N; ++j) { //遍历每一列
cout << (a[i] == j ? "X" : "-") << " ";;
} //如果标记数组中这一行的皇后放在j位置,则输出X,否则输出-,
//用空格分隔
cout << endl; //每一行输出一个换行
}
cout << endl; //每一组数据一个换行分隔
}
void addQueen(int *a, int n) {
if (n > N) { //n代表从第一行开始放置
printSol(a);
total_cnt++;
return ;
}
for (int i = 1; i <= N; ++i) { //i从第1列到第N列遍历
if (isPlaceOK(a, n, i)) {
a[n] = i; //如果可以放置,就把皇后放在第n行第i列
addQueen(a, n + 1);
}
}
}
int main() {
addQueen(arr, 1);
cout << "total: " << total_cnt << " solutions.\n";
return 0;
}
Pascal
program queen; var a:array[1…8]of longint; //记录皇后的行坐标
b,c,d:array[-7…16]of longint; //行,右上,右下斜线的占位标志 m,ans:longint;
procedure queen(j:longint); var i:longint; begin
if j>8 then
begin
inc(ans); //满足条件,找到一种方案
exit;
end;
for i:=1 to 8 do//每个皇后位置有八种可能
if(b[i]=0)and(c[i+j]=0)and(d[j-i]=0)then //如果位置没有被占则运行
begin
a[j]:=i; //皇后放置在此行
b[i]:=1; //占领第i行
c[i+j]:=1; //占领右上
d[j-i]:=1; //占领右下
queen(j+1); //递归
b[i]:=0; //回溯,恢复行占位标志
c[i+j]:=0; //回溯,恢复斜上方(右上)占位标志
d[j-i]:=0; ///回溯,恢复斜下方(右下)占位标志
end; end; begin //主程序
for m:=-7 to 16 do //数据初始化为0
begin
b[m]:=0; //行数据初始化为0
c[m]:=0; //右上数据初始化为0
d[m]:=0; //右下数据初始化为0
end;
ans:=0;
queen(1); //开始放置第一个皇后
writeln(ans); end.
Java
public class Queen {
private int[] column; //同栏是否有皇后,1表示有
private int[] rup; //右上至左下是否有皇后
private int[] lup; //左上至右下是否有皇后
private int[] queen; //解答
private int num; //解答编号
public Queen() {
column = new int[8+1];
rup = new int[(2*8)+1];
lup = new int[(2*8)+1];
for (int i = 1; i <= 8; i++)
column[i] = 0;
for (int i = 1; i <= (2*8); i++)
rup[i] = lup[i] = 0; //初始定义全部无皇后
queen = new int[8+1];
}
public void backtrack(int i) {
if (i > 8) {
showAnswer();
} else {
for (int j = 1; j <= 8; j++) {
if ((column[j] == 0) && (rup[i+j] == 0) && (lup[i-j+8] == 0)) {
//若无皇后
queen[i] = j; //设定为占用
column[j] = rup[i+j] = lup[i-j+8] = 1;
backtrack(i+1); //循环调用
column[j] = rup[i+j] = lup[i-j+8] = 0;
}
}
}
}
protected void showAnswer() {
num++;
System.out.println("\n解答" + num);
for (int y = 1; y <= 8; y++) {
for (int x = 1; x <= 8; x++) {
if(queen[y]==x) {
System.out.print("Q");
} else {
System.out.print(".");
}
}
System.out.println();
}
}
public static void main(String[] args) {
Queen queen = new Queen();
queen.backtrack(1);
}
}
Erlang
-module(queen).
-export([printf/0, attack_range/2]).
-define(MaxQueen, 4).%寻找字符串所有可能的排列
%perms([]) ->% [[]];
%perms(L) ->% [[H | T] || H <- L, T <- perms(L -- [H])].
perms([]) ->[[]];
perms(L) ->[[H | T] || H <- L, T <- perms(L -- [H]), attack_range(H,T) == []].printf() ->L = lists:seq(1, ?MaxQueen),io:format("~p~n", [?MaxQueen]),perms(L).
%检测出第一行的数字攻击到之后各行哪些数字%left向下行的左侧检测%right向下行的右侧检测
attack_range(Queen, List) ->attack_range(Queen, left, List) ++ attack_range(Queen, right, List).attack_range(_, _, []) ->[];
attack_range(Queen, left, [H | _]) when Queen - 1 =:= H ->[H];
attack_range(Queen, right, [H | _]) when Queen + 1 =:= H ->[H];
attack_range(Queen, left, [_ | T]) ->attack_range(Queen - 1, left, T);attack_range(Queen, right, [_ | T]) ->attack_range(Queen + 1, right, T).
python
def queen(A, cur=0):
if cur == len(A):
print(A)
return 0
for col in range(len(A)): # 遍历当前行的所有位置
A[cur] = col
for row in range(cur): # 检查当前位置是否相克
if A[row] == col or abs(col - A[row]) == cur - row:
break
else: # 如果完成了整个遍历,则说明位置没有相克
queen(A, cur+1) # 计算下一行的位置
queen([None]*8)
C#
using System;
using System.Collections.Generic;
namespace EightQueens_CSharp {
public class EightQueens {
private List<int[]> solutions;
public List<int[]> GetSolutions(int queenCount) {
solutions = new List<int[]>();
List<int> queenList = new List<int>();
for (int i = 0; i < queenCount; i++) {
queenList.Add(0);
}
putQueen(queenCount, queenList, 0);
printSolutions(solutions);
return solutions;
}
private void putQueen(int queenCount, List<int> queenList, int nextY) {
for (queenList[nextY] = 0; queenList[nextY] < queenCount; queenList[nextY]++) {
if (checkConflict(queenList, nextY) == false) {
nextY++;
if (nextY < queenCount) {
putQueen(queenCount, queenList, nextY);
}
else {
solutions.Add(queenList.ToArray());
Console.WriteLine(string.Join(", ", queenList));
}
nextY--;
}
}
}
private bool checkConflict(List<int> queenList, int nextY) {
for (int positionY = 0; positionY < nextY; positionY++) {
if (Math.Abs(queenList[positionY] - queenList[nextY]) == Math.Abs(positionY - nextY) || queenList[positionY] == queenList[nextY]) {
return true;
}
}
return false;
}
private void printSolutions(List<int[]> solutions) {
int count = 0;
foreach (var solution in solutions) {
Console.WriteLine("Solution: {0}", count++);
int queenCount = solution.Length;
for (int i = 0; i < queenCount; i++) {
printLine(solution[i], queenCount);
}
Console.WriteLine("------------------");
}
}
private void printLine(int pos, int width) {
for (int i = 0; i < width; i++) {
if (pos == i) {
Console.Write(" x");
}
else {
Console.Write(" .");
}
}
Console.WriteLine();
}
}
}
Scheme
#lang racket (define (enumerate-interval low high) (cond ((> low high) null)
((= low high) (list high))
(else (cons low (enumerate-interval (+ 1 low) high)))))
(define (flatmap proc seq) (foldr append null (map proc seq)))
(define empty-board null)
(define (safe? test-column positions) (define (two-coordinate-safe? coordinate1 coordinate2)
(let ((row1 (row coordinate1))
(row2 (row coordinate2))
(col1 (column coordinate1))
(col2 (column coordinate2)))
(if (or (= row1 row2)
(= (abs (- row1 row2)) (abs (- col1 col2))))
#f
#t))) (let ((test-coordinate (get-coordinate-by-column test-column positions)))
(foldr (lambda (coordinate results)
(and (two-coordinate-safe? test-coordinate coordinate)
results))
#t
(remove test-coordinate positions))))(define (adjoin-position new-row new-column existing-positions) (cons (make-coordinate new-row new-column)
existing-positions))
(define (make-coordinate row column) (list row column)) (define (row coordinate) (car coordinate)) (define (column
coordinate) (cadr coordinate)) (define (get-coordinate-by-column
target-column coordinates) (cond ((null? coordinates) null)
((= target-column (column (car coordinates))) (car coordinates))
(else (get-coordinate-by-column target-column (cdr coordinates)))))(define (queens board-size) (define (queen-cols k) (if (= k 0) (list empty-board) (filter (lambda (positions) (safe? k positions)) (flatmap (lambda (rest-of-queens) (map (lambda (new-row) (adjoin-position new-row k rest-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (- k 1)))))) (queen-cols board-size)) ;敲(queens 8),得到运行结果,每个坐标,第一个代表行,第二个代表列 ;纯递归思路,运行环境racket 7.5
Lua
local num = 0;
local t = socket.gettime();
local function searchQueen(tab)
local data = clone(tab)
if data[2] == #data[1] then
num = num + 1
-- return print("result..", num, " ==> ", table.concat(data[1], ","))
return
end
data[2] = data[2] + 1;
for i = 1, 8 do
data[1][data[2]] = i
local bOk = true
for j = 1, data[2]-1 do
if (data[1][j] == i) or (math.abs(i - data[1][j]) == data[2] - j) then
bOk = false
break
end
end
if bOk then
searchQueen(data)
end
end
end
searchQueen({{0,0,0,0,0,0,0,0}, 0})
print("EIGHT QUEEN result == ", num, "\ntime cost == ", socket.gettime() - t)