八皇后問題(N皇后問題)

八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。
首先來看看這張模擬八皇后的圖。
這張圖說明皇后具備橫軸、豎軸以及兩個斜軸方向的殺傷力,也就是像米字形同樣;
爲了減小判斷,咱們按照一個方向往另外一個方向排列,中間不能跳行,這樣咱們就能夠只判斷已經有皇后的位置,尚未皇后的就能夠偷懶不用判斷了。
個人方案是:1.從最下面開始排列,而後往上添加,從左往右排列,這樣就只須要判斷比本身Y座標低的具備殺傷能力的位置有沒有皇后就OK
        方法是把本身假定要放置皇后的位置的X和Y軸都依據判斷特性進行處理;例如,左斜線X和Y軸都減1;中間的只須要把Y軸減1;右邊的和左邊的相反,X軸加1,Y軸減1;注意處理邊界問題。
      2.爲了找到合適的位置咱們須要在查找失敗的時候具有回溯的能力,就須要退回到前一行(Y=Y-1,注意XY是否到邊界),直至能回溯或者所有判斷完畢,每次回溯的時候記得X軸要從頭開始
      3.經過一個數據結構記錄正在查找的方案,經過另外一個數據結構記錄已經找到的方案,固然也能夠用一個變量記錄方案個數
下面這張黑色背景是其中一個方案的截圖,第一行表明皇后的座標xy;後面的是棋盤,這裏輸出豎軸是x,橫軸是y,從上到下,從左到右,其中*是邊界,空格是空區,#是皇后。
#include <iostream>
#include <cstring>
#include "DTString.h"
#include "LinkList.h"  // 這裏使用鏈表存儲皇后的位置

using namespace std;
using namespace DTLib;

template <int SIZE>             // N皇后問題,SIZE表示皇后個數或者棋盤大小
class QueenSolution : public Object
{
protected:
    enum { N = SIZE + 2 };      // N表示棋盤大小,爲了邊界識別,棋盤四周都要加一格

    struct Pos : public Object  // 方位結構體
    {
        Pos(int px = 0, int py = 0) : x(px), y(py) { }
        int x;
        int y;
    };

    int m_chessboard[N][N];     // 棋盤,0表示空位,1表示皇后,2表示邊界
    Pos m_direction[3];         // 共3個方向;方向-一、-1表示左斜線;0、-1表示下方;一、-1表示右斜線;首先從最下方開始,因此只需考慮下面的行。
    LinkList<Pos> m_solution;   // 用鏈表記錄解決方案,注意該鏈表只記錄一個方案,而不是所有
    int m_count;                // 記錄有效方案數量

    void init()          // 初始化函數
    {
        m_count = 0;                    // 有效方案初始化爲0

        for(int i=0; i<N; i+=(N-1))     // 設置棋盤邊界,遍歷第1行和最後一行
        {
            for(int j=0; j<N; j++)      // 遍歷每一列
            {
                m_chessboard[i][j] = 2; // 給棋盤的上下設置邊界
                m_chessboard[j][i] = 2; // 給棋盤的左右設置邊界
            }
        }

        for(int i=1; i<=SIZE; i++)      // 初始化棋盤爲空位,注意0是邊界,因此從1開始
        {
            for(int j=1; j<=SIZE; j++)
            {
                m_chessboard[i][j] = 0;
            }
        }

        m_direction[0].x = -1;          // 初始化方向數據
        m_direction[0].y = -1;
        m_direction[1].x = 0;
        m_direction[1].y = -1;
        m_direction[2].x = 1;
        m_direction[2].y = -1;
    }

    void print()                        // 打印有效方案,方案記錄了座標值
    {
        for(m_solution.move(0); !m_solution.end(); m_solution.next())   // 打印座標
        {
            cout << "(" << m_solution.current().x << ", " << m_solution.current().y << ")" ;
        }

        cout << endl;

        for(int i=0; i<N; i++)          // 打印棋盤
        {
            for(int j=0; j<N; j++)
            {
                switch(m_chessboard[i][j])
                {
                    case 0: cout << " "; break; // 空位
                    case 1: cout << "#"; break; // 皇后
                    case 2: cout << "*"; break; // 邊界
                }
            }

            cout << endl;
        }

        cout << endl;                           // 棋盤打印完換行
    }

    bool check(int x, int y, int d)             // 檢查是否可放置皇后(xy是假定要放置皇后的座標,d是要判斷有沒有皇后的方向值)
    {
        bool flag = true;

        do                       // 查詢座標直至邊界或皇后
        {
            x += m_direction[d].x;        // 座標向下減小
            y += m_direction[d].y;
            flag = flag && (m_chessboard[x][y] == 0);// 查看座標位置是否有空位()
        }
        while( flag );

        return (m_chessboard[x][y] == 2);       // 判斷do循環以後的xy值是否到邊界,返回真就是到邊界可放置皇后,不然就是有皇后不能放置
    }

    void run(int j) // 檢查當前行有沒有可放置皇后的位置
    {
        if( j <= SIZE ) // 檢查當前行在棋盤內,注意不要跑到邊界上
        {
            for(int i=1; i<=SIZE; i++)                       // 遍歷當前行的全部列
            {
                if( check(i, j, 0) && check(i, j, 1) && check(i, j, 2) )    // 檢查當前位置是否可放置皇后,須要判斷3個方向
                {
                    m_chessboard[i][j] = 1;                                 // 若是能夠放置就標記該處有皇后

                    m_solution.insert(Pos(i, j));                           // 記錄皇后的位置到鏈表

                    run(j + 1);                                             // 遞歸判斷下一行

                    m_chessboard[i][j] = 0;                                 // 返回後要把棋盤當前位置的皇后清除,避免下一次調用時還有上次的皇后位置;

                    m_solution.remove(m_solution.length() - 1);             // 回溯後記錄皇后位置的鏈表長度也要減小,由於以前的方案無效或已經輸出;
                }
            }
        }
        else    // 若是j大於SIZE就表示一輪檢查結束,方案計數加1並打印方案
        {
            m_count++;

            print();
        }
    }

public:
    QueenSolution()
    {
        init();
    }

    void run()
    {
        run(1); // 從第一行開始,注意邊界佔用的行數(本例四周都佔用了1行或列記錄邊界,因此從1開始)

        cout << "Total: " << m_count << endl;   // 輸出方案個數
    }
};

int main()
{
    QueenSolution<8> qs;

    qs.run();

    return 0;
}

本例來自於狄泰《數據結構》45課第3節整理。ios

相關文章
相關標籤/搜索