数据结构——递归(回溯算法实现八皇后问题)

a 夏天 提交于 2020-01-27 17:32:34

递归——回溯算法

八皇后问题介绍

八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8x8国际象棋棋盘上拜访8个皇后,使其不能够相互攻击,即:任意两个皇后都不能够处于同一行,同一列,同一斜线上,问共有多少种摆法?

解题思路:

1、第一个皇后先放在第一列上
2、第二个皇后在放在第二行的第一列上,然后判断是否符合条件,如果不符合,放在第二列,第三列。。。 如果符合,那么继续
3、继续第三个皇后,还是从第一列开始,然后第二列。。。当到最后第8个皇后也放在一个不冲突的位时,证明得到了一个正确的解
4、当有一个正确的解后,在栈退回到上一个栈时,回溯开始,最后就会将第一个皇后在第一列中所有解全部得到
5、然后在回头继续将第一个皇后放到第一行的第二列上,继续上面操作

注:我们通过一个一维数组就可以来解出八皇后的所有解,数组下标值可以认为是行,每个下标值对应的数据是在这一行中,皇后所在的列。因为数组下标为0,所以我们将行和列都从0开始

写三个方法:
1、用来放置皇后的方法
2、用来判断当前放置的皇后与已经放置的皇后是否冲突
3、打印方法

代码:

package cn.littleworm;
/*
    八皇后问题:
        任意两个皇后不能够出现在同一行,同一列,同意斜线上
        解题思路:
            1、第一个皇后先放在第一列上
            2、第二个皇后在放在第二行的第一列上,然后判断是否符合条件,如果不符合,放在第二列,第三列。。。
               如果符合,那么继续
            3、继续第三个皇后,还是从第一列开始,然后第二列。。。当到最后第8个皇后也放在一个不冲突的位时
               ,证明得到了一个正确的解
            4、当有一个正确的解后,在栈退回到上一个栈时,回溯开始,最后就会将第一个皇后在第一列中所有解全部得到
            5、然后在回头继续将第一个皇后放到第一行的第二列上,继续上面操作
 */



public class Queue {

    //定义一共有多少的皇后
    int max = 8;
    //定义一个数组,来存放每次皇后放置的位置
    int[] array = new int[max];
    //定义计数器,记录共有多少中解法
    static int count = 0;
    static int judgetnum = 0;


    //能够将数组中的数据打印
    private void print(){
        //只要触发了该方法,就将计数器加一
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
    }

    //判读放入的皇后与前面已经放入的皇后是否冲突:n表示第n个皇后
    private boolean judget(int n){
        /*
            皇后冲突的条件是“:任意两个皇后,不能够出现在同一行,同一列,同意斜线
            所以:
                同一列:array[n] == array[i]
                同意斜线:Math.abs(n-i) == Math.abs(array[n]-array[i])

         */
        for (int i=0;i<n;i++){
            if (array[n]==array[i]||Math.abs(n-i)==Math.abs(array[n]-array[i])){
                return false;   //证明出现冲突
            }
        }
        return true;
    }

    //放置第n个皇后
    public void put(int n){
        //如果已经将前八个皇后都防止好了
        if (n==max){
            //将数组中的数据打印
            print();
            return;
        }

        //依次放皇后,从第一列开始
        for (int i = 0; i < max; i++) {
            //先放在第一列
            array[n] = i;
            //判断是否冲突,如果不冲突,递归调用
            if (judget(n)){
                put(n+1);
            }
            //如果冲突的话就会继续将皇后向下一列放
        }
    }

    public static void main(String[] args) {

        Queue queue = new Queue();
        queue.put(0);   //从第1行第一列开始放置
        System.out.printf("共有%d种摆法",count);


    }

}

结果:

结果
一共有92种解法,我们可以在小游戏中试验一下是否正确:
游戏结果
我们可以从92中结果中随便找到一种结果,进行实验,结果总是正确的。

难点

在代码中有两个难点,一个是如何判断是否冲突,一个是如何理解回溯

判断是否冲突:

问题规定:任意两个皇后不能出现在同一行,同一列,同意斜线上。
因为我们是使用的一维数组,所以不需要考虑能不能出现在同一行上,只需要考虑会不会出现在同一列上和同意斜线上

列冲突

棋盘最大列数是8,所以我们只需要考虑当前第n个皇后与前面第n-1个皇后是否冲突交就行了。因为数组中存放的数据就是皇后所在的列数,所以使用一个循环,遍历比较当前array[n]的数据与前array[i]的数据是否相同就可。若相同则冲突

斜线冲突

不难发现,当两个皇后出现在同一斜线时:它们两个的行的差的绝对值与列的差的绝对值是一样的,即Math.abs(n-i) == Math.abs(array[n]-array[i])

理解回溯

  //放置第n个皇后
    public void put(int n){
        //如果已经将前八个皇后都防止好了
        if (n==max){
            //将数组中的数据打印
            print();
            return;
        }

        //依次放皇后,从第一列开始
        for (int i = 0; i < max; i++) {
            //先放在第一列
            array[n] = i;
            //判断是否冲突,如果不冲突,递归调用
            if (judget(n)){
                put(n+1);
            }
            //如果冲突的话就会继续将皇后向下一列放
        }
    }

回溯在放置皇后的方法中使用到,当我们放入第一个皇后,是放在第一行第一列,这时判断是否冲突?不冲突,那么调用if判断语句中的put(n+1)方法(递归调用)。这时的参数就是2了,即第二个皇后,同样先放到第二行的第一列,在判断,发现冲突,根据循环,将皇后移动到第二列,判断,冲突,继续后移,再判断,冲突,继续后移,不冲突,然后调用if判读语句中的put(n+1)语句(递归调用)…当最后调用put(n+1)的时候发现参数已经大于8,所以将数组中的数据打印出来,并调用reutrn语句,弹出当前这个方法栈,进入上一级的方法栈,也就是进入第8行,循环继续执行,发现一直冲突,当这个方法栈的循环指向完了,在跳到上一级方法栈,就是第7行,然后在第7行寻找其他符合条件的位置。。。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!