1. 程式人生 > >面試題:八皇后問題(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