八皇后问题

基本介绍

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

问题分析

设八个皇后为xi,分别在第i行( I = 1,2,3,4……,8)。
问题的解状态:可以用

(1,x1),(2,x2),……,(8,x8)

表示8个皇后的位置。由于行号固定,可简单记为:

(x1,x2,x3,x4,x5,x6,x7,x8)

所以我们可以直接用一个长度为8的数组,来表示八皇后问题的一组解。
问题的解空间:

(x1,x2,x3,x4,x5,x6,x7,x8),1≤xi≤8(i=1,2,3,4……,8)

共88个状态。
约束条件:八个皇后位置 (1,x1),(2,x2),……,(8,x8)不在同一行、同一列和同一对角线上。

方法一:暴力穷举

每一行放一个皇后,可以放在第 1 列,第 2 列,……,直到第8列。穷举所有的可能,检验皇后之间是否会相互攻击。

毫无疑问,这种方法是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。

代码如下:

public class EightQueens {
    // 方法一:暴力枚举
    public List<int[]> eightQueens1(){
        ArrayList<int[]> result = new ArrayList<>();
        // 用一个数组保存一组解
        int[] solution = new int[8];
// 遍历解空间
        for (solution[0] = 0; solution[0] < 8; solution[0]++){
            for (solution[1] = 0; solution[1] < 8; solution[1]++){
                for (solution[2] = 0; solution[2] < 8; solution[2]++){
                    for (solution[3] = 0; solution[3] < 8; solution[3]++){
                        for (solution[4] = 0; solution[4] < 8; solution[4]++){
                            for (solution[5] = 0; solution[5] < 8; solution[5]++){
                                for (solution[6] = 0; solution[6] < 8; solution[6]++){
                                    for (solution[7] = 0; solution[7] < 8; solution[7]++){
                                        if (check(solution))
                                    result.add(Arrays.copyOf(solution, 8));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    // 定义一个判定有效的方法
    private boolean check(int[] a){
        // 任意两个皇后位置比较
        for (int i = 0; i < 7; i++){
            for (int j = i + 1; j < 8; j++){
                if (a[i] == a[j] || Math.abs(a[i] - a[j]) == j - i )
                    return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:O(N^N)。这里N为皇后数量,即n皇后问题的维度,本题N=8。 八皇后问题如果用穷举法,需要尝试88 =16,777,216种情况。而这里的check方法,又需要C28 = 28次比较。

方法二:回溯法

回溯算法优于穷举法。首先,将第一行的皇后放在第一列。之后第二行的皇后,也从放在第一列开始判断,这时已经发生冲突。于是调整第二行的的皇后到第二列,继续冲突就放第三列,直到不冲突为止。

如此可依次放下后续每一行的皇后。当发现某一行的皇后无处放置时,就回溯到上一行,将皇后位置向后继续调整到另一个不冲突的地方。如果上一行也无处放置,继续向上回溯。直到每一行都无法继续放置,遍历结束。

判断冲突的方法改进
我们发现,一个皇后是否跟其它有冲突,主要取决于当前位置所在的横、纵、斜4条线。
在这里插入图片描述
目前我们直接考虑每一行放置一个皇后,那么横向直线就不用考虑了,只需要考虑其它三条线。纵向比较简单,只要判断同一列是否有皇后就可以了;而对斜向仔细研究规律,可以发现,同一斜线上的格子,横纵坐标是有规律的:

  • 方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等
  • 方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等
    在这里插入图片描述
    在这里插入图片描述
    所以为了在代码中快速判断,当前某个皇后位置是否有效,可以增加三个辅助集合:
  • col:记录某一列上是否出现过皇后;
  • diag1:记录某一左上-右下方向的斜线上,是否出现过皇后;
  • diag2:记录某一右上-左下方向的斜线上,是否出现过皇后。
    每次放置皇后时,对于每个位置判断其是否在三个集合中,如果三个集合都不包含当前位置,则当前位置是可以放置皇后的位置。

代码如下:

// 定义辅助集合
HashSet<Integer> cols = new HashSet<>();
HashSet<Integer> diags1 = new HashSet<>();
HashSet<Integer> diags2 = new HashSet<>();

// 方法二:回溯法
public List<int[]> eightQueens(){
    ArrayList<int[]> result = new ArrayList<>();
    int[] solution = new int[8];
    Arrays.fill(solution, -1);    // 初始填充-1 
    // 传入行号0,开始调用
    backtrack(result, solution, 0);
    return result;
}
// 定义一个回溯方法
private void backtrack(ArrayList<int[]> result, int[] solution, int row){
    if (row >= 8){
        result.add(Arrays.copyOf(solution, 8));
    } else {
        // 遍历每一列,考察可能的皇后位置
        for (int column = 0; column < 8; column ++){
            if (cols.contains(column))
                continue;
            int diag1 = row - column;
            if (diags1.contains(diag1))
                continue;
            int diag2 = row + column;
            if (diags2.contains(diag2))
                continue;
            solution[row] = column;    // 当前位置可以放置皇后
            cols.add(column);
            diags1.add(diag1);
            diags2.add(diag2);
            // 递归调用,找下一行的皇后
            backtrack(result, solution, row + 1);
            // 回溯状态
            solution[row] = -1;
            cols.remove(column);
            diags1.remove(diag1);
            diags2.remove(diag2);
        }
    }
}

复杂度分析

  • 时间复杂度:O(N!),其中 N 是皇后数量。回溯的过程,其实就是N的一个全排列。
  • 空间复杂度:O(N),其中 N是皇后数量。空间复杂度主要取决于递归调用层数、记录每行放置的皇后的列下标的数组以及三个集合,递归调用层数不会超过 N,数组的长度为 N,每个集合的元素个数都不会超过N。
  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值