递归算法求解八皇后问题实验报告

一、背景介绍

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解。
八皇后问题最早是由国际象棋棋手马克斯·贝瑟尔(Max Bezzel)于1848年提出。第一个解在1850年由弗朗兹·诺克(Franz Nauck)给出。并且将其推广为更一般的n皇后摆放问题。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。
在此之后,陆续有数学家对其进行研究,其中包括高斯和康托,1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。
1972年,艾兹格·迪杰斯特拉用这个问题为例来说明他所谓结构化编程的能力。他对深度优先搜索回溯算法有着非常详尽的描述。
八皇后问题在1990年代初期的著名电子游戏《第七访客》和NDS平台的著名电子游戏《雷顿教授与不可思议的小镇》中都有出现。

二、算法流程介绍

(1)基本算法思路:

八皇后的问题是在一个8*8的棋盘上, 保证每个皇后的每行每列以及对角线上没有其他皇后。我们可以采用递归算法+回溯算法思想找到所有满足条件的解。
首先,在第n(n从1开始)行上找到可以放置皇后的所有位置,若该位置满足条件(每个皇后的每行每列以及对角线上没有其他皇后,以下简称条件),则在第n+1行去找到所有可以放置皇后的位置。若该位置不满足条件,则该位置不能放置皇后,尝试该行下一个位置,若到该行的最后一个位置都不符合条件,则返回到上一行,尝试上一行的下一个位置。直到能在第8行上找到可以成功放置皇后的位置时,则得到一个正确的解。由不断递归和回溯,可以得到所有满足条件的问题的正确的解。
由上思路分析,每一行去确定皇后放置的位置的做法相同,该算法不断去寻找下一行的符合条件的皇后的位置,因此使用了递归的算法。若遇到皇后放置位置不符合条件的情况(这里分为两种):第一种是在该行的非最后的一个位置不满足条件时,尝试该行的下一个位置。第二种是该行的最后一个位置不符合条件时,回到上一行尝试上一行的下一个位置,从而尝试其他可能的正确的解,这就是回溯算法的思想。

(2)程序算法思路

这里使用java语言来实现八皇后问题的求解。
解决八皇后问题的程序算法流程可以分为以下几个步骤:

第一: 定义一个8行的一维数组来表示棋盘上的每个格子是否放置了皇后(可以使用二维数组模拟棋盘,也可以使用一维数组,因为一维数组较简便,这里使用一维数组)。数组的下标0~7 代表棋盘的每一行,数组中元素的值0~7代表每一行八皇后对应的列,由此来模拟棋盘。

int[] board = new int[8];

解释:这里使用数组board来模拟棋盘,该数组有8个元素,数组的下标代表行数,数组中元素的值代表皇后所在的列数。如数组boder = {0,4,7,5,2,6,1,3}的一种解代表的棋盘如下(Q代表皇后 *代表空位)

第八行 * * * Q * * * *
第七行 * Q * * * * * *
第六行 * * * * * * Q *
第五行 * * Q * * * * *
第四行 * * * * * Q * *
第三行 * * * * * * * Q
第二行 * * * * Q * * *
第一行 Q * * * * * * *

第二: 定义一个检测函数来检查当前位置是否可以放置皇后,即是否满足不同行、不同列、不同斜线的条件。这个函数通过遍历之前放置的皇后的位置,判断当前放置的皇后位置是否满足条件。

public boolean suitable(int[] board, int n) {
    for (int i = 0; i < n; i++) {
        if(board[i] == board[n] || Math.abs(n-i) == Math.abs(board[n]-board[i]))
            return false;
    }
    return true;
}

解释:这里使用函数suitable函数来作为检测函数,该函数的形参为棋盘数组board,和要检测的皇后的所在行n(这里n代表数组下标该行已放置皇后)。For循环遍历要检测的皇后所在行的前所有行(1~n-1行),找到前n-1行的所有皇后进行位置条件的判断,因为使用的是一维数组,数组下标代表行数,所以必定没有皇后在同一行的情况,因此只需判断前n-1行的皇后和当前第n行的皇后是否在同一列(board[i]==board[n])和是否在同一斜行(Math.abs(n-i) == Math.abs(board[n]-board[i]))的情况。

第三: 定义一个回溯函数来递归地尝试在每一行放置皇后,其中通过检查函数来判断皇后是否满足约束条件。如果满足,则继续处理下一行;如果不满足,则回退到上一行,撤销之前的选择,并尝试其他的位置。

public void eightQueueQuestion(int[] board, int n) { //n表示行
    if(n==8) { //找到最后一行并确定后
        System.out.println("第" + (++count) + "种情况");
        printBoard(board); //打印数组
        return;
    }
    for (int i = 0; i < 8; i++) {
        board[n] = i;//放入对应的n行 i列代表的位置
        if (suitable(board, n)) { //若该数组中前n-1行和 第n行皇后位置不冲突 则放入位置合适 到此放入一个皇后
         eightQueueQuestion(board,n+1); //找到下一行的正确位置
        }
    }
}

解释:这里使用eightQueueQuestion函数来作为回溯函数,该函数实参board为棋盘数组,n为棋盘数组的行数(数组的下标),for循环从第该行第一个位置开始,先放入数组中元素,代表皇后所在列,再判断该位置是否合适,若合适则调用eightQueueQuestion(board,n+1)函数找到下一行皇后合适的位置,不合适则尝试该行下一个位置。该行位置都不合适则返回上次调用该函数的位置,判断上一行下一个元素是否合适,直到当n=8时,前8行都找到了皇后合适的位置,则找到一个正确的解。

第四: 定义一个打印函数,可以将用一维数组模拟的棋盘通过输出屏幕的方式转换成平面棋盘,并可以直观的看出满足条件的解的皇后放置的位置情况。

public void printBoard(int[] board) {
    int boardRowAndCol = board.length;
    for (int i = boardRowAndCol-1; i >=0; i--) { //从最后一行开始打印
        int queenStation = board[i];//皇后的位置
        for (int j = 0; j < boardRowAndCol; j++) {
            if(j == queenStation) {
                System.out.print("Q ");
                continue;
            }
            System.out.print("* ");
        }
        System.out.println();//换行
    }
}

解释:这里用printBoard函数作为打印函数,该函数的形参为棋盘数组。函数中定义了一个boardRowAndCol变量记录数组的长度(棋盘的列数)第一个for循环确定打印棋盘的行数,这里从最后一行开始打印,queenStation代表每一行皇后的位置(在棋盘的那一列)。第二个for循环确定棋盘打印的列数,在每一行通过变量QueenStation在对应的列表示出皇后。

第五: 定义一个计数器变量记录找到的可行解的个数。当递归到最后一行时,说明找到了一个可行解,将计数变量加一并将当前方案通过打印函数输出。

public static int count = 0;
这里使用静态变量count作为计数器,赋初始值为0if(n==8) { //找到最后一行并确定后
    System.out.println("第" + (++count) + "种情况");
    printBoard(board); //打印数组
    return;
}

解释:这里在找到符合条件的解后通过count计数器来统计解的个数,每次找到一个解就将count加1,并打印到屏幕上,随后通过打印函数输出对应解的情况

第六: 从第一行开始调用回溯函数,遍历所有可能的位置,最终得到可行解的个数和所有可行解的具体方案。

public static void main(String[] args) {
    int[] board = new int[8];//0 ~ 7 下标代表0~7行 对应的值代表每一行的位置
    new EightQueens().eightQueueQuestion(board, 0);
}

解释:这里通过main方法调用回溯函数,通过创建对象来调用对象中的函数,得到八皇后问题解的个数和所有解的情况。

三、程序代码

public class EightQueens {
    public static int count = 0;
    public static void main(String[] args) {
        int[] board = new int[8];//0 ~ 7 下标代表0~7行 对应的值代表每一行的位置
        new EightQueens().eightQueueQuestion(board, 0);
    }

    public void eightQueueQuestion(int[] board, int n) { //n表示行数
        if(n==8) { //找到最后一行并确定后
            System.out.println("第" + (++count) + "种情况");
            printBoard(board); //打印数组
            return;
        }
        for (int i = 0; i < 8; i++) {
            board[n] = i;//放入对应的n行 i列代表的位置
            if (suitable(board, n)) { //若该数组中前n-1行和 第n行不冲突 则放入位置合适 到此放入一个
             eightQueueQuestion(board,n+1); //找到下一行的正确位置
            }
        }
    }
    public boolean suitable(int[] board, int n) {
        for (int i = 0; i < n; i++) {
            if(board[i] == board[n] || Math.abs(n-i) == Math.abs(board[n]-board[i]))
                return false;
        }
        return true;
    }
    public void printBoard(int[] board) {
        int boardRowAndCol = board.length;
        for (int i = boardRowAndCol-1; i >=0; i--) { //从最后一行开始打印
            int queenStation = board[i];//皇后的位置
            for (int j = 0; j < boardRowAndCol; j++) {
                if(j == queenStation) {
                    System.out.print("Q ");
                    continue;
                }
                System.out.print("* ");
            }
            System.out.println();//换行
        }
    }
}

四、运行结果展示

4*4棋盘上(n=4的解 共2种)
在这里插入图片描述

8*8棋盘上(n=8的解 共92种)

在这里插入图片描述

在这里插入图片描述

五、总结

八皇后问题是一个经典的回溯算法问题,要求在8×8的国际象棋棋盘上放置八个皇后,使得任何两个皇后都不能在同一行、同一列或同一斜线上。通过程序实现可知这个问题有92种可行的解,由查阅资料可知其中有12种独立解。

本实验报告使用Java语言实现了八皇后问题的求解程序,采用了递归和回溯的方法。程序的基本思路是:从第一行开始,依次尝试每一列,如果当前位置不会与之前放置的皇后冲突,就将皇后放在该位置,并递归地尝试下一行;如果当前位置会与之前放置的皇后冲突,就回溯到上一行,尝试其他列。当所有行都放置了皇后时,就得到了一个可行的解,并输出结果;当所有列都尝试过后,没有找到可行的解时,就结束程序。

本实验报告运行了八皇后问题的求解程序,并输出了所有92种可行的解。程序运行时间为0.02秒,占用内存为2.3MB。程序运行结果表明,递归和回溯是一种有效的求解八皇后问题的方法。

本篇完 感谢您的观看

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MrkWinter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值