【概念】
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
【八皇后问题】
国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
【基本思想】
八皇后问题就是回溯算法的典型,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。
【分析】
为了分析简捷,以4皇后为例:
首先利用回溯算法,先给第一个皇后安排位置,如下图所示,安排在(1,1)然后给第二个皇后安排位置,可知(2,1),(2,2)都会产生冲突,因此可以安排在(2,3),然后安排第三个皇后,在第三行没有合适的位置,因此回溯到第二个皇后,重新安排第二个皇后的位置,安排到(2,4),然后安排第三个皇后到(3,2),安排第四个皇后有冲突,因此要回溯到第三个皇后,可知第三个皇后也就仅此一个位置,无处可改,故继续向上回溯到第二个皇后,也没有位置可更改,因此回溯到第一个皇后,更改第一个皇后的位置,继续上面的做法,直至找到所有皇后的位置,如下图所示。
这里为什么我们用4皇后做例子呢?因为3皇后是无解的。
【代码】
首先,伪代码
//寻找当前行的皇后应该位于哪一列
void FindQueen(row)
{
for(int i=0;i<8;i++)
{
//1.判断当前是否满足要求
if(Is_Meet(row,i))
{
//2.判断当前是否是最后一行了
if(最后一行){输出操作,并返回}
//3.执行下一行匹配
FindQueen(row+1);
//4.如果进行到这一步,说明步骤三已经操作完,没有合适结果,需要返回上一步,即执行当前for的下一个循环
}
}
}
具体实现:
public class EightQueen {
private int queen[] = {-1,-1,-1,-1,-1,-1,-1,-1};
int total = 0;
/**
* 判断是否冲突
* @param row
* @param column
* @return false 冲突 true 无冲突
*/
public boolean isMeet(int row, int column){
int c;
for(int r=0; r<row; r++){
c = queen[r];
/*同一行*/
if(column == c)
return false;
/*正对角线*/
if((row + column) == (c + r))
return false;
/*反对角线*/
if((column - row) == (c - r))
return false;
}
return true;
}
/**
* 在棋盘上摆放皇后
* 通过递归(堆栈原理)实现的回溯
* @param row
*/
public void findQueen(int row){
for(int c=0; c<8; c++){
if(isMeet(row, c)){
queen[row] = c;
if(row == 7){
total++;
display();
return;
}
/*递归,放置下一枚皇后*/
findQueen(row + 1);
}
/*发生冲突的情况,没有符合条件的结果,需要查看for的下一个循环,
* 如果循环结束仍然没有符合的条件,则返回上一步-回溯
*/
queen[row] = -1;
}
}
public void display(){
for(int i=0; i<8; i++){
int inner;
for(inner=0; inner<queen[i]; inner++)
System.out.print("0");
System.out.print("#");
for(inner=queen[i]+1; inner<8; inner++)
System.out.print("0");
System.out.print("\n");
}
System.out.println("==========================");
}
public int getTotal(){
return total;
}
public static void main(String[] args){
EightQueen eightQueen = new EightQueen();
eightQueen.findQueen(0);
int total = eightQueen.getTotal();
System.out.println("total is --> " + total);
}
}
==========================================================================
二、循环解法
/**
* @author
* @version V1.0
* @Date 2018年1月30日 上午9:00:10
* @Email
* @Description: 八皇后
* 这是按列逐行循环的方法,不是回溯的方法,貌似更简单一些
*/
public class EightQueen {
public static int num = 0; //累计方案总数
public static final int MAX_QUEEN = 8; //皇后个数
/* cols[col] = row 表示第 col 列的第 row 行放置一个皇后 */
public int cols[] = new int[MAX_QUEEN];
public void handler(){
//从第0列开始
getArrangement(0);
System.out.println(MAX_QUEEN + "皇后问题共有" + num + "种解法");
}
/**
* 算法核心函数
* @param n 当前列
*/
private void getArrangement(int n){
/**
* 遍历该列所有不合法的行,并用rows数组记录,不合法即rows[i]=true
*/
boolean[] rows = new boolean[MAX_QUEEN];
for(int i=0; i<n; i++){
//不能在同一行
rows[cols[i]] = true;
int d = n - i;
/**
* 不能在同一反斜线'\'
*/
if(cols[i]-d >=0)
rows[cols[i] - d] = true;
/**
* 不能在同一斜线'/'
*/
if((cols[i] + d) < MAX_QUEEN)
rows[cols[i] + d] = true;
}
/**
* 当前皇后归位
*/
for(int i=0; i<MAX_QUEEN; i++){
//判断该行是否合法
if(rows[i])
continue;
//设置当前列合法棋子所在行
cols[n] = i;
//当前列不为最后一列时
if(n < MAX_QUEEN-1){
//递归
getArrangement(n+1);
}else{
//累计方案个数
num++;
printChessBoard();
}
}
}
/**
* 打印棋盘信息
*/
private void printChessBoard() {
// TODO Auto-generated method stub
System.out.println("第" + num + "种走法 ");
for(int i=0; i<MAX_QUEEN; i++){
for(int j=0; j<MAX_QUEEN; j++){
if(i == cols[j]){
System.out.printf("%2s", 0);
}else{
System.out.printf("%2s", "*");
}
}
System.out.println();
}
}
public static void main(String[] args) {
EightQueen eq = new EightQueen();
eq.handler();
}
}
【参考】
8皇后问题(java算法实现)