zoukankan      html  css  js  c++  java
  • 回溯算法之八皇后问题

    一、什么是回溯算法?

    我们肯定都玩过迷宫游戏吧,比较复杂的迷宫,肯定是不可能第一遍就直接过了,只能一步一步地进行尝试。当走到一个死胡同时,只能退回到上一个分岔口进行重新选择。
    数独游戏也是这样的,对于一个不确定的方格,我们就会先将这个方格可能出现的问题记录下来,一个一个地尝试,直到得到正确解。有着“通用解”称呼
    所以,回溯算法就是类似于枚举的算法,将这一步的所以可能性一个一个地进行尝试。上边迷宫中的分岔口和数独中的可能出现多个数字的方格就是“回溯点”

    二、有什么优缺点?

    1. 因为要对每一个点的可能情况都进行枚举测试,所以效率特别低,比如后边下边例子中的“八皇后问题”中,总共要进行15000次左右的运算,虽然对于计算机来说是很快的,但是更加复杂的问题,可能会更多
    2. 它可是有着“通用解”称呼,基本上大多数的这类问题都可以用此方法解决。

    小鱼吃猫

    三、经典的“八皇后”问题

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。

    使用Java来解决此问题主要有以下步骤:

    1. 设计一个数据模型来解决此问题,
    private int result[] = new int[8];
    

    首先想到的就是一个二维数组,但是我在学习的时候,看到老师用了一个长度为8的一维数组就解决了,思想如下:

    • 使用int[8]来表示一个正确解,也就是说,这个数组中的8个数组分别表示第一行、第二行、第三行......直到第八行中正确的棋子的位置,比如其中一个解如下图,它所表示的结果就是:{1,5,8,6,3,7,2,4}
      八皇后问题
    1. 找到“回溯点”
    • 第一种回溯情况:
      这个问题的解决方案就是从每一行的每一个点开始摆放棋子,如果这个棋子可以话这里,就继续放置下一行。如果不能放置在这里,就往后移,直到这一行的8个位置全都不能的话,就往上回溯一行,将上一行的棋子往右移一行,再继续放置这一行,如果上一行全部放了个遍还是不行,就再往上回溯一行,就这样继续。。。
    • 第二种回溯情况:
      因为要找到所有的问题,第二个回溯情况就是当一种摆法摆放完成后。从最后一行进行回溯,比如上边的解中{1,5,8,6,3,7,2,4},当最后一个4摆放完成后,就将最后一个放在5这个位置上,不行的话就放6,7,8,如果全都不行,就将上一行中的2放在3位置上,再从第八行的1,2,3...这样放
    1. 写出这个判断这个回溯点的条件方法
     /**
         * 判断这一行中的棋子摆放的是否可用
         * 主要就是判断从第一行到第n-1行的这几个皇后会不会和第n行的皇后相互攻击
         * 因为之前的n-1行之间之前就已经判断了的,如果它们之前不满足条件就不会到第n行的
         *
         * @param n 传入的这一行
         * @return
         */
        private boolean check(int n) {
            timeCount++;
            for (int i = 0; i < n; i++) {
                /*
                第一个条件就是判断它们是不是在同一行
                这里主要想一个如何判断它们是不是在同一斜行上,
                因为我们的模型就是使用数组的角标来表示它当前在第i+1行,用元素表示它在这一行的第i+1个位置上
                 */
                if (result[i] == result[n] || Math.abs(n - i) == Math.abs(result[n] - result[i])) {
                    return false;
                }
            }
            return true;
        }
    

    4.开始写放置棋子的方法

    /**
         * 最关键的问题,使用递归来进行回溯算法
         * 我们的思路就是一行一行的放置
         *
         * @param row 在第几行摆放棋子
         */
        public void calResult(int row) {
    
            if (row > 7) {
                //此时就说明第8行已经摆完了,且是正确的
                sum++;
                printResult();
                return;
            }
    
            //这个for循环就是在这一行从第1个开始放,一直放到第max个,
            for (int i = 0; i < max; i++) {
                result[row] = i;
                if (check(row)) {
                    //说明这一行检验是通过的
                    calResult(row+1);
                }
                //如果这个位置不满足,就会自动执行下一次循环,也就是i++
                //如果这一行全部试完之后,还是不合格,就会自动放到回溯到上一行去
            }
        }
    
    

    最后的代码如下:

    package cn.lyn4ever.structure.excerise;
    
    public class EightQueenDemo {
    
    
        /**
         * 用来表示摆放皇后的结果,
         * 数组的下标表示第i+1行的棋子,值表示棋子放在这一行的第i+1个位置
         * 如:
         * result[0]=0  表示在第一行的第一个位置上有一个棋子
         */
        private int max = 8;
        private int result[] = new int[max];
        private int sum = 0;//用来记录总结果数
        private int timeCount = 0;//用来记录所有操作的次数
    
    
        /**
         * 判断这一行中的棋子摆放的是否可用
         * 主要就是判断从第一行到第n-1行的这几个皇后会不会和第n行的皇后相互攻击
         * 因为之前的n-1行之间之前就已经判断了的,如果它们之前不满足条件就不会到第n行的
         *
         * @param n 传入的这一行
         * @return
         */
        private boolean check(int n) {
            timeCount++;
            for (int i = 0; i < n; i++) {
                /*
                第一个条件就是判断它们是不是在同一行
                这里主要想一个如何判断它们是不是在同一斜行上,
                因为我们的模型就是使用数组的角标来表示它当前在第i+1行,用元素表示它在这一行的第i+1个位置上
                 */
                if (result[i] == result[n] || Math.abs(n - i) == Math.abs(result[n] - result[i])) {
                    return false;
                }
            }
            return true;
        }
    
        /**
         * 打印这个结果数组
         */
        private void printResult() {
            for (int i = 0, length = result.length; i < length; i++) {
                System.out.print(result[i] + " ");
            }
            System.out.println();
        }
    
        /**
         * 最关键的问题,使用递归来进行回溯算法
         * 我们的思路就是一行一行的放置
         *
         * @param row 在第几行摆放棋子
         */
        public void calResult(int row) {
    
            if (row > 7) {
                //此时就说明第8行已经摆完了,且是正确的
                sum++;
                printResult();
                return;
            }
    
            //这个for循环就是在这一行从第1个开始放,一直放到第max个,
            for (int i = 0; i < max; i++) {
                result[row] = i;
                if (check(row)) {
                    //说明这一行检验是通过的
                    calResult(row+1);
                }
                //如果这个位置不满足,就会自动执行下一次循环,也就是i++
                //如果这一行会部试完之后,还是不合格,就会自动放到回溯到上一行去
            }
        }
    
    
        public static void main(String[] args) {
    
            EightQueenDemo eightQueenDemo = new EightQueenDemo();
            eightQueenDemo.calResult(0);
            System.out.printf("八皇后问题的解法一共有%d", eightQueenDemo.sum);
            System.out.printf("一共进行了%d次运算", eightQueenDemo.timeCount);
        }
    
    }
    
    

    最后的运行结果如下:
    八皇后问题

  • 相关阅读:
    第3课 线性分类器损失函数与最优化
    李飞飞机器视觉课程笔记:第2课 K最近邻与线性分类器
    周志华《机器学习》第二章学习笔记
    通过anaconda安装tensorflow
    周志华《机器学习》第一章学习笔记 奥卡姆剃刀原理、没有免费的午餐定理(NFL)、回归模型
    DPM目标检测模型
    损失函数的理解
    mini-batch的理解
    前向传播、后向传播
    SVM(支持向量机)的理解
  • 原文地址:https://www.cnblogs.com/Lyn4ever/p/12181741.html
Copyright © 2011-2022 走看看