八皇后(递归+回溯)

数据结构——八皇后

题目介绍

八皇后问题是一个古老而又著名的问题,是学习回溯算法的一个经典案例。今天我们就一起来探究一下吧!

image-20210516183336943

时间退回到1848年,国际西洋棋棋手马克斯·贝瑟尔提出了这样的一个问题,

在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问一共有多少种摆法。

大数学家高斯认为一共有76种摆法,1854年在柏林的象棋杂志上不同的作者发表了共计40种不同的见解,后来还有人利用图论的方法得出共有92种摆法。

而如今,通过我们的计算机以及编程语言我们可以轻松的解决这个问题。

最直接的也是最容易想到的一种解法便是暴力法,我们可以在8×8的格子中任选8个皇后,选定后看是否满足任意两个皇后都不处于同行同列同斜线的条件,若满足则累计满足条件的方案。学习过排列组合的我们发现64取8这个数字达到了40亿,显然是令人难以接受的。

但我们根据这个条件,我们可以人为地做出一些选择,比如根据条件我们可知每行每列最多都只能有一个皇后,这样可以在一定程度上缩减问题的规模。在第一行的某一列选择放置一个皇后,共有8种不同的选择,而第二行只能选择剩下的7列,也就是7种选择,剩下每一行的选择都会递减1,那么总共可供选择的方案有8的阶乘种,已经是一种远优于暴力解法的解法,但是这个阶乘的时间复杂度恐怕也难以令人接受,还有更优的解法吗?

那是自然的,这便是递归回溯的方法

当我们选择了第一个皇后的位置之后,与其处于同行同列同斜线的位置便都无法被选择,第二个皇后只能放在未被第一个皇后所辐射到的位置上,接着放置第三个皇后,同样不能放在被前两个皇后辐射到的位置上,若此时已经没有未被辐射的位置能够被选择,也就意味着这种摆法是不可行的,我们需要回退到上一步,给第二个皇后重新选择一个未被第一个皇后辐射的位置,再来看是否有第三个皇后可以摆放的位置,如还是没有则再次回退至选择第二个皇后的位置,若第二个皇后也没有更多的选择则回退到第一个皇后,重新进行位置的选择。

image-20210516183644616

思路分析

1、第一个皇后放在第一行第一列

2、第二和皇后放在第二行第一列,然后判断是否合乎规则,如果正确,则继续放第三个皇后,如果不正确,则继续放在第二列、第三列……,直到找到一个适合的。

3、继续放置第三个,还是按照同样的方法,……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解。

4、当得到一个正确解之后,在退回到上一个,就会开始回溯,即将第一个皇后,放到第一列正确的所有正确解。

5、然后,接续第一个皇后放第二列,后面继续执行1,2,3,4的步骤。

说明: 理论上,应该创建一个二维数组表示棋盘,但是实际上可以通过算法,使用一个一维数组就可以解决问题。

如: arr[8] = {0, 4, 7, 5, 2, 6, 1, 3}

上个数组就是该问题的一个正确解。在这里有一个规律:该数组的下标【0……7】表示了该皇后所处的行号,下标的值val 表示了该皇后所处的列号。如0表示的意思就是:第一皇后的位置是第一行的第一列,同样的4表示的意识是:第二个皇后的位置是第2行的第5列

代码实现

public class Queen {

    private int max = 8;

    //用一维数组来表示二维数组
    //{0 4 7 5 2 6 1 3 },数组下标i表示第i行,val 表示第val列。
    private int[] array = new int[max];
    private static int count  = 0;
    public static void main(String[] args) {

        Queen queen = new Queen();
        queen.check(0);
        System.out.println();
        System.out.println("解法一共有"+count);
    }
    private void check(int n)
    {
        if(n == max)
        {
            print();
            return;
        }
        //循环的是列
        for(int i = 0;i < max;i++)
        {
            //把当前的Queen放在该行的第一列上
            array[n] = i;
            //进行判断
            if(judge(n))
            {
                check(n+1);
            }
        }
    }

    //当放置第n个位置的皇后是,判断改皇后是否冲突
    private boolean judge(int n)
    {
        for(int i = 0;i < n;i++)
        {
            // array[i] == array[n]: 由于在一维数组中 val表示Queen所处的列,
            //由于其规则决定了,在数组中不能存在相同的值,也就是不用处在同一列上。
            // Math.abs(n - i) == Math.abs(array[n] - array[i])
            //由于在一维数组中 下标表示Queen所处的行,
            // Math.abs(n - i)表示了 二者之间的行所差的数量,
            // Math.abs(array[n] - array[i]) 表示了二者之间列所差的数量。如果相同,则说明在同义斜线上
            if(array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i]))
            {
                return false;
            }
        }
        return true;
    }

    //输出8皇后的位置
    private void print()
    {
        count++;
        for(int i = 0;i < max;i++)
        {
            System.out.print(array[i]+" ");
        }
        System.out.println();
    }
}

代码是比较简单的,接下来是说如何实现的递归和回溯

对于java这个语言来说,一个类中的方法,其实虚拟机栈中的一个个的栈帧。当当前的方法执行完毕之后,就会弹出栈顶,继续执行下一个栈帧。

image-20210516190234148

内存结构图大致如图所示,通过递归不断调用时,就会生成一个个的栈帧,通过8次的递归调用,当第八个皇后的8次for循环执行完毕之后,就会弹出栈顶,继续执行第7个皇后的剩余的for循环。只到第1个皇后的8次for循环执行完毕。通过这样的方式就产生了回溯。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值