每日一題17:八皇后問題

八皇后問題是由國際西洋棋棋手馬克斯·貝瑟爾於1848年提出的:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 高斯認爲有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。解題的思路如下:從棋盤的第一行起,先選擇第一個格子作爲第一個皇后的位置,然後在第二行中從第一個格子開始試探一個合適的位置放置第二個皇后,接下來在第三行從第一個格子開始試探一個合適的位置放置第三個皇后…以此類推,知道第八個皇后找到合適的位置。至此第一個滿足要求的擺法出現。因爲在每一行都是從第一個格子開始往後搜索的,爲了窮盡所有的擺法,將最後一個皇后在最後一行中繼續後挪,檢查是否還有滿足要求的擺法。當最後一行探測完畢,返回到上一行,將第七個皇后往後探測是否還有合適的位置,如果在第七行找到另一個滿足要求的位置,那麼又來到第八行,從第一個格子開始往後探測是否有放置第八個皇后的位置;如果第七行沒有找到另一個合適的位置,那麼程序轉入第六行,將第六個皇后依次往後挪,探測另一個能夠放置第六個皇后的位置…依次類推,這也就是回溯所要表達的意思:從某點A開始往前走了幾步,當問題得到求解或不能得到解,再回到點A,朝其他方向繼續往前走,探測問題的解或更多的解。
檢查某個位置B是否可以放置一個皇后時,由於程序對棋盤是從上往下一行一行遍歷的,所以只需要檢查該位置以上的格子上是否有使得該位置不能放置一個皇后的格子:1)B位置以上同列的格子;2)B位置左斜對角線上方的格子;3)B位置右斜對角線上方的格子;行不用檢測,因爲一行只能放置一個皇后,如果已經存在一個皇后與B同行,該檢查不會進行。

棋盤數據結構與創建棋盤的代碼

#include "stdafx.h"

#include <iostream>

using namespace std;


const int candidate_pos = 0;
const int legal_pos = 1;

struct chessboard
{
    int size;
    int *board;
};

chessboard* create(int size)
{
    chessboard* cb = new chessboard;
    cb->size = size;
    cb->board = new int[size*size];
    memset(cb->board,0,size*size*sizeof(int));
    return cb;
}

打印棋盤的代碼:

void print(chessboard* cb)
{
    int size = cb->size;
    for(int i = 0; i < size; ++i)
    {
        for(int j = 0; j < size; ++j)
            cout<<cb->board[i*size + j]<<' ';
        cout<<endl;
    }
    cout<<endl;
}

檢查某位置是否合適放置一個皇后的代碼:

bool check(chessboard* cb,int row,int col)
{
    int size = cb->size;
    int* board = cb->board;

    //檢查列
    int* p = &board[col];
    for (int i = 0; i < row; ++i)
    {
        if(*p) return false;
        p += size;
    }

    //檢查從左往右下降的對角線
    for (int i = row - 1,j = col - 1; i >= 0 && j >= 0; --i,--j)
    {
        if(board[i*size + j]) return false;
    }

    //檢查從右往左下降的對角線
    for (int i = row - 1,j = col + 1; i >= 0 && j < size; --i,++j)
    {
        if(board[i*size + j]) return false;
    }
    return true;
}

搜索八皇后位置的代碼:

void _solve(chessboard** cb,int i,int j,int &count)
{
    //在行或列的維度上,任一維到了盡頭,則本行搜索結束,返回上一行
    if(i >= (*cb)->size || j >= (*cb)->size) return;
    int size = (*cb)->size;
    int* board = (*cb)->board;

    //找到一個可以放置皇后的位置後
    if(check(*cb,i,j))
    {
        //將該位置設爲1,表示放置了一個皇后
        board[i*size + j] = legal_pos;

        //如果是最後一行,那表示得到了一個解嘍
        if(i == size - 1)
        {
            ++count;
            print(*cb);
        }
        //否則往下一行搜索下一個皇后的位置
        else _solve(cb,i + 1,0,count);

        //不管後面的搜索找到幾個解,都需要把在本行剛
        //才找到皇后挪開,回溯過程纔可能是正確的
        board[i*size + j] = candidate_pos;
    }
    //對於每一行中的j位置,不管它是一個可以或不可以放置
    //皇后的位置,都要繼續往後搜索,以窮盡所有的解
    _solve(cb,i,j + 1,count);
}

int solve_eightqueen(int size)
{
    int count = 0;
    chessboard* cb = create(size);
    //從第一個位置開始搜索嘍
    _solve(&cb,0,0,count);
    return count;
}

測試代碼:

int _tmain(int argc, _TCHAR* argv[])
{
    cout<<"=============1============"<<endl;
    int count = solve_eightqueen(1);
    cout<<"Total:"<<count<<endl;

    cout<<"=============2============"<<endl;
    count = solve_eightqueen(2);
    cout<<"Total:"<<count<<endl;

    cout<<"=============3============"<<endl;
    count = solve_eightqueen(3);
    cout<<"Total:"<<count<<endl;

    cout<<"=============4============"<<endl;
    count = solve_eightqueen(4);
    cout<<"Total:"<<count<<endl;

    cout<<"=============5============"<<endl;
    count = solve_eightqueen(5);
    cout<<"Total:"<<count<<endl;
    return 0;
}

程序運行截圖:
這裏寫圖片描述
這裏寫圖片描述
棋盤取1到5時,得到的結果。
由於當棋盤大小爲8*8時,結果太多,有92個,所以只截取4個結果作爲示例:
這裏寫圖片描述
實際上,上面的算法是一種暴力解法,試圖探索所有的排列。按照每行一個皇后的排法一共有8^8種排列,也就是2^24 = 16777216個,這是一個很大的數,但是程序卻可以才很短的時間內得到答案,這主要是因爲前面行的搜索共同限制了後面行中一些位置的搜索,這使得許許多多的排列不用去考察,因而使得問題可以快速求解。可以把棋盤大小設爲12,那麼總的搜索空間大小變爲:8916100448256(八萬億),而使用的時間呢:
這裏寫圖片描述
這並不是說回溯法多麼好,而是問題如此,它前面的搜索結果深刻影響了後面的搜索,而回溯法只是恰好適合解決這類問題,這是一種比較自然地思維方式而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章