面试题:八皇后问题(N皇后问题)

简介: 前言八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?这道题目也可以稍微延伸一下,变为 N×N的棋盘上放置N个皇后,其他条件相同。

前言

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?这道题目也可以稍微延伸一下,变为 N×N的棋盘上放置N个皇后,其他条件相同。
下面介绍一种比较简单易懂的实现方式。

项目下载地址

正文

算法

先说一下算法, 这里使用的是一个改良版的广度优先搜索算法。在N×N的棋盘上,我们先在第一行的第一个位置放置下皇后,接着我们就不去管第一行了,因为第一行已经不能放置皇后了。我们在第二行找到所有的可以放置皇后的位置。同理我们现在可以不用去管前两行了。我们对于第二行的每一个可以放置皇后的位置,都在第三行继续寻找可以放置皇后的位置,如此往复,直到我们遍历到最后一行。这个时候我们就得到了一部分解,这些解是对于第一个皇后放置在第一行第一列的位置而言。接下来对于第一行第二列、第三列…所有列都进行这个步骤,就得到了所有的解。

代码

为了更加直观,我们模拟出一个N×N的棋盘。我们把每次放置一个皇后之后的局面称为一个状态(State)。下面是State类的代码:

import java.util.ArrayList;
import java.util.List;

public class State {
    private List<Point> pointList = new ArrayList<Point>();
    private int lineNum;

    public List<Point> getPointList() {
        return pointList;
    }

    public int getLineNum(){
        return lineNum;
    }

    public void setLineNum(int lineNum){
        this.lineNum = lineNum;
    }

}

每个state对象有两个属性,pointList存放的是当前的state下已经放置的皇后坐标,lineNum是当前state所遍历到的行数。其中用到的Point类代码如下:

public class Point{
    private int X;
    private int Y;

    public Point(int x, int y){
        this.X = x;
        this.Y = y;
    }

    public int getX(){
        return this.X;
    }

    public int getY(){
        return this.Y;
    }

    public void setX(int x){
        this.X = x;
    }

    public void setY(int y){
        this.Y = y;
    }

}

每个Point对象有一个X坐标和一个Y坐标。
下面是主程序:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class EightQueen {
    //起始状态列表
    public static List<State> startStates = new ArrayList<State>();

    //棋盘的行列数和要放置的皇后数量
    public static final int lineNum = 4;

    //一个N×N的棋盘
    public static Point[][] allPoints = new Point[lineNum][lineNum];

    //解法数量
    public static int count = 0;

    public static void main(String[] args) {

        //初始化棋盘
        for(int i=0; i<lineNum; i++){
            for(int j=0; j<lineNum; j++){
                allPoints[i][j] = new Point(i, j);
            }
        }

        //初始化起始状态列表。每个State的PointList分别存放了第一行的8个坐标,并且设置第一行为遍历初始行
        for(int i=0; i<lineNum; i++){
            State state = new State();
            state.getPointList().add(new Point(0, i));
            state.setLineNum(0);
            startStates.add(state);
        }

        //对于初始化state列表中的每个state,进行遍历操作。
        for(State state : startStates){
            calculate(state);
        }
        System.out.println("总数为:" + count); 
    }

    public static void calculate(State state)
    {
        Stack<State> stack = new Stack<State>();
        stack.push(state);
        while(!stack.isEmpty()){
            //从stack里取出一个状态
            State state2 = stack.pop();
            //如果已经遍历到最后一行,输出这个解
            if(state2.getLineNum() == lineNum - 1){
                for(Point goalpoint : state2.getPointList()){
                    for(int i=0; i<lineNum; i++){
                        if(i!=goalpoint.getY())
                            System.out.print("_ ");
                        else
                            System.out.print("Q ");
                    }
                    System.out.println(); 
                }
                System.out.println();
                count++;
                continue;
            }

            //否则寻找下一行可以放置皇后的位置
            int currentLineNum = state2.getLineNum() + 1;
            for(Point point : allPoints[currentLineNum]){
                //如果该点可以放置皇后
                if(isSatisfied(point, state2.getPointList()))
                {
                    //创建一个state对象
                    State newState = new State();
                    //把这个新的state的pointList设置为前一个点的pointList里的所有点加上当前的点的坐标
                    for(Point point2 : state2.getPointList()){
                        newState.getPointList().add(new Point(point2.getX(), point2.getY()));
                    }
                    newState.getPointList().add(point);
                    //设置新的state的行数为下一行
                    newState.setLineNum(currentLineNum);
                    //入栈
                    stack.push(newState);
                }
            }
        }
    }

    //判断一个点是否可以放置皇后
    public static boolean isSatisfied(Point point, List<Point> list){
        for(Point point2 : list){
            //两个皇后不能再同一条横线、直线、斜线上。由于我们直接遍历的是下一行的点,所以肯定不会出现X坐标相同的情况
            if(point2.getY() == point.getY() 
                    || Math.abs(point2.getX() - point.getX()) == Math.abs(point2.getY() - point.getY()))
                return false;
        }
        return true;
    }

}

程序的输出为

_ Q _ _ 
_ _ _ Q 
Q _ _ _ 
_ _ Q _ 

_ _ Q _ 
Q _ _ _ 
_ _ _ Q 
_ Q _ _ 

总数为:2

如果我们更改lineNum为6,输出为

_ Q _ _ _ _ 
_ _ _ Q _ _ 
_ _ _ _ _ Q 
Q _ _ _ _ _ 
_ _ Q _ _ _ 
_ _ _ _ Q _ 

_ _ Q _ _ _ 
_ _ _ _ _ Q 
_ Q _ _ _ _ 
_ _ _ _ Q _ 
Q _ _ _ _ _ 
_ _ _ Q _ _ 

_ _ _ Q _ _ 
Q _ _ _ _ _ 
_ _ _ _ Q _ 
_ Q _ _ _ _ 
_ _ _ _ _ Q 
_ _ Q _ _ _ 

_ _ _ _ Q _ 
_ _ Q _ _ _ 
Q _ _ _ _ _ 
_ _ _ _ _ Q 
_ _ _ Q _ _ 
_ Q _ _ _ _ 

总数为:4

由于lineNum = 8的时候输出太长,这里不做展示。结果的数量为92种。
这里附上不同lineNum对应的解法数量:

lineNum     solution(lineNum)
1           1  
2           0  
3           0  
4           2  
5           10  
6           4  
7           40  
8           92  
9           352  
10          724  
11          2680  
12          14200  
13          73712  
14          365596  
15          2279184  
16          14772512  
17          95815104  
18          666090624  
19          4968057848 
20          39029188884  
21          314666222712  
22          2691008701644  
23          24233937684440  
24          227514171973736  
25          2207893435808352  
相关文章
|
机器学习/深度学习 算法
面试题 08.12:八皇后
面试题 08.12:八皇后
88 0
|
机器学习/深度学习 算法 C++
Interview:算法岗位面试—上海某公司算法岗位(偏机器学习,互联网金融行业)技术面试考点之数据结构相关考察点—斐波那契数列、八皇后问题、两种LCS问题
Interview:算法岗位面试—上海某公司算法岗位(偏机器学习,互联网金融行业)技术面试考点之数据结构相关考察点—斐波那契数列、八皇后问题、两种LCS问题
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
11月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
11月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
11月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
264 4
|
12月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
1253 2
|
12月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
152 0