利用递归和回溯解决八皇后问题

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用递归和回溯解决八皇后问题相关的知识,希望对你有一定的参考价值。

前言

本文解题思路来自于b站up懒猫老师的视频: 懒猫老师-C语言-递归函数-八皇后问题(搜索,回溯)
这个老师讲的特别细,有兴趣的可以去看看,相信一定能获得比本文更大的收获。

八皇后问题

在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?
高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。 —— 百度百科

解题思路

1️⃣ 每一行不能重复出现,可以每次只在同一行上放置一个皇后,则避免了行冲突的问题。每一行的上皇后的放置位置可以从第1列~第8列,共8种选择。例如:从第一行开始放置皇后,那么该皇后从第1行第1列到第1行第8列都可以放置。

2️⃣ 每一列不能重复出现,可以在当前行的某一列合适位置放置皇后,并标记该列为已被占领,则下一行的皇后不可以放置在该列上,例如: 第一行第一列放置皇后,则第一列不能再放置其它皇后。

3️⃣ 每一对角线不能重复出现,对角线有主对角线(左上至右下)和副对角线(左下至右上)两个区域,当把本行皇后放置在合适列上后,标记这两个对角线区域,不能使其它皇后放置在这一区域中。

标记对角线的方式为:

主对角线,将棋盘每一行的行下标分别减去每一列的列下标,得到的值在每一个主对角线的位置上都是相等的。

可以定义一个boolean型数组d1,用来纪录棋盘中主对角线是否被占用,用上图中每一个对角线的值作为索引,因为存在负数因此整体+7得到0~14的值,刚好可以作为该数组的索引,数组长度为15。

假设当前放置皇后的位置为(0,2),则d1[0-2+7]d1[5]的值被置为false。当放置下一行的皇后时,例如在(1,3)位置,则判断数组d1[1-3+7]位置上的值是否为true,为true表示可以放置,此时应该为false,因此这个位置不能放置。

副对角线,将棋盘每一行的行下标分别加上每一列的列下标,得到的值在每一个副对角线的位置上都是相等的。

同样可以定义一个d2数组用来纪录棋盘中副对角线是否被占用,长度同样是15,并且值已经是0~14所以无须修改。待放入皇后位置的行下标+列下标既是对应在d2数组中的下标值。

通过上面分析,可以定义4个数组:

  1. 数组place为int型数组,用来纪律每一行皇后的位置,数组长度为8。假如第一行皇后放置再第三列的位置,则place[0] = 2
  2. 数组flag为boolean型数组,用来纪录列的占用情况,数组长度也为8,默认值为true。假如第一列放置了一个皇后,则flag[0] = false
  3. 数组d1为boolean型数组,用来纪录棋盘中主对角线是否被占用,长度为15,默认值为true。
  4. 数组d2为boolean型数组,用来纪律棋盘中副对角线是否被占用,长度为15,默认值为true。
public class Queen
    // 数据结构
    private int[] place = new int[8];  		         // 表示棋盘的一行
    private boolean[] flag = new boolean[8]; 		 // 纪录列的占用情况
    private boolean[] d1 = new boolean[15];          // 纪录主对角线的占用情况
    private boolean[] d2 = new boolean[15];          // 纪录副对角线的占用情况

4️⃣ 定义数组后,可以从第1行的第1列开始放置第一个皇后,接着放置下一行的皇后,如果出找到合适位置则放置,当找到第8行合适放置的位置时,纪录本次8个皇后放置的情况,并将第8个皇后的纪录情况置为true,回到第上一行,继续查找,直到遍历完所有情况退出。

当遍历到第n行时,第1列和第8列都找不到合适的位置时,则也需要回溯到上一行,将上一行皇后的位置后移到本行其它的合适位置,继续查找。因此,需要用到使用递归来实现回溯的功能。具体代码如下:

public class Queen
    private int[] place = new int[8];  		        // 表示棋盘的一行
    private boolean[] flag = new boolean[8]; 		 // 纪录列的占用情况
    private boolean[] d1 = new boolean[15];    // 纪录主对角线的占用情况
    private boolean[] d2 = new boolean[15];  // 纪录副对角线的占用情况
    private int count; // 用于纪录成功放置8皇后的次数

    public Queen() 
        // 初始化数组的值
        Arrays.fill(flag,true);
        Arrays.fill(d1,true);
        Arrays.fill(d2,true);
    

    public void putQueen(int row) // row表示当前行
        for(int col = 0; col < 8; col++) // 从第row的第1列向第8列遍历
            if(flag[col] && d1[row-col+7] && d2[row+col])
                // 放置皇后
                place[row] = col;
                // 标记占用
                flag[col] = false;      // col列被占用
                d1[row-col+7] = false;  // 对应主对角线被占用
                d2[row+col] = false;    // 对应副对角线被占用
                // 当row < 7 时
                if(row < 7) // 递归调用
                    putQueen(row+1); // 继续放置下一行皇后
                 else  // 当row == 7 时, 递归终止
                    print(); // 输出本次皇后的摆放位置
                

                // 回溯,当代码执行到此处有2种情况:
                // 1. 从putQueen()递归方法中返回, 并且在后续递归中未能找到合适位置, 则需要将row行,col列的所有标记清除, 并继续遍历row行col+1列。
                // 2. 已经成功放置8个皇后, 即进入else语句, 同样也需要将row行,col列的所有标记清除,并返回至上一级方法调用处, 如果上一行皇后的位置不在最后一列,则会继续往后查找其它情况。如果上一行皇后在最后列, 则会继续返回上一行重复操作。
                // 清除标记
                flag[col] = true;
                d1[row-col+7] = true;
                d2[row+col] = true;
            
        
    

    // 输出本次皇后摆放位置
    public void print()
        // 纪录次数
        System.out.println("第" + (++count) + "次八皇后的排列方式: ");
        // 利用place数组中存放的信息, 还原为一个二维数组
        // 'Q'表示皇后,  '.' 表示空
        char[][] chessBoard = new char[8][8];
        for(int i = 0; i < 8; i++)
            Arrays.fill(chessBoard[i],'.');
            // 将第i行的第place[i]列置为Q
            chessBoard[i][place[i]] = 'Q';
        

        // 遍历
        for(char[] chars : chessBoard)
            for(char chess : chars)
                System.out.print(chess + "\\t");
            
            System.out.println();
        
    

    @Test
    public void testQueen()
        putQueen(0); // 从第1行开始
    

输出结果: (有兴趣的可以验证一下)

1次八皇后的排列方式: 
Q	.	.	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	.	Q	.	.	
.	.	Q	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	Q	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.2次八皇后的排列方式: 
Q	.	.	.	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	.	.	.	.	.	Q	
.	.	Q	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.	
.	Q	.	.	.	.	.	.	
.	.	.	.	Q	.	.	.3次八皇后的排列方式: 
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	.	.	.	.	.	Q	
.	Q	.	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	.	Q	.	.	.	.	.4次八皇后的排列方式: 
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	.	Q	
.	Q	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	Q	.	.	.	.	.5次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	.	.	.	.	.	Q	
.	.	Q	.	.	.	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	.	Q	.	.	.6次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	Q	.	
Q	.	.	.	.	.	.	.	
.	.	Q	.	.	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	.	Q	.	.	
.	.	.	Q	.	.	.	.7次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	.	Q	.	.	
.	.	Q	.	.	.	.	.8次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	.	Q	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	Q	.	.	.	.	.	
.	.	.	.	Q	.	.	.9次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	.	.	.	.	.	Q	
.	.	Q	.	.	.	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	.	Q	.	.	.10次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	Q	.	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	Q	.	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.11次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	.	Q	
Q	.	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.	
.	.	Q	.	.	.	.	.12次八皇后的排列方式: 
.	Q	.	.	.	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	.	Q	.	.	
Q	.	.	.	.	.	.	.	
.	.	Q	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.13次八皇后的排列方式: 
.	.	Q	.	.	.	.	.	
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	.	Q	.	.	.	
.	.	.	.	.	.	.	Q	
.	Q	.	.	.	.	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.14次八皇后的排列方式: 
.	.	Q	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	Q	.	.	.	.	.	.	
.	.	.	.	.	.	.	Q	
Q	.	.	.	.	.	.	.	
.	.	.	.	.	.	Q	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	Q	.	.15次八皇后的排列方式: 
.	.	Q	.	.	.	.	.	
.	.	.	.	Q	.	.	.	
.	Q	.	.	.	.	.	.	
.	.	.	.	.	.	.	Q	
.	.	.	.	.	Q	.	.	
.	.	.	Q	.	.	.	.	
.	.	.	.	.	.	Q	.	
Q	.	.	.	.	.	.	.16次八皇后的排列方式: 
.	.	Q	.	.	.	.	.	
.	.	.	.	Q	.	.	以上是关于利用递归和回溯解决八皇后问题的主要内容,如果未能解决你的问题,请参考以下文章

八皇后问题算法详解

迷宫回溯和八皇后问题

算法入门经典-第七章 例题7-2 八皇后问题

详解Java递归(Recursion)通过递归解决迷宫回溯及八皇后问题

Python用迭代(yield)和递归解决八皇后问题

回溯法八皇后问题(递归和非递归)