1. 程式人生 > >馬踏棋盤C++實現與貪婪演算法優化

馬踏棋盤C++實現與貪婪演算法優化

馬踏棋盤問題即在一個8*8的棋盤上,指定一個起點,找到一個路徑,讓棋子按照馬走日的規則將所有格子無重複的遍歷一次。

這個問題是在學習資料結構與演算法的時候看到的,當時看的是C語言的版本,也沒記住具體的解法,事後回顧起來覺得很有意思,於是自己用C++編寫了一段程式碼嘗試一下。

首先進行分析:從資料結構的角度來分析,一個棋盤可以看作一個二維陣列。然後是對每次落子進行分析,根據馬走日的規則,假設不考慮是否會出現越界情況,則在某位置X上,下一步落子共有八種可能性,如圖所示,按照順時針順序依次標記為0,1,2,3,4,5,6,7:

- 7 - 0 -
6 - - - 1
- - X - -
5 - - - 2
- 4 - 3 -

那麼顯然易見,已知X的情況下,利用深度優先遍歷的方式,是一定可以得到一個路徑滿足要求的,只不過這麼做的計算複雜度非常之高。有了想法,當然是以能跑出結果為導向先寫出大概的程式碼才行,於是我按照這個思路寫出了原始版本:

/*
 * 馬踏棋盤最原始的深度優先演算法
 * 利用棧
 */
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int chessX = 8;
const int chessY = 8;
static bool chess[chessX][chessY];//棋盤,遍歷過的位置進行標記
struct Id{
    int x;
    int y;
    Id(int theX,int theY):x(theX),y(theY) {};
    ~Id() = default;
};
stack step;//棧,依次存放每次遍歷的座標,回溯的時候出棧
bool run(int,int);
int judge(int,int,int &);//判斷路徑,返回0-7表示選擇的路徑,返回則需要回溯
bool isInChess(int,int,int);//判斷某個方向的落子是否在棋盤內
bool isNeverRun(int,int,int);//判斷落子是否被遍歷過
Id next(int,int,int);//返回cnt方向的落子位置
int main()
{
    memset(chess,0x00,sizeof(chess));
    auto start = chrono::system_clock::now();
    run(0,7);
    auto end = chrono::system_clock::now();
    auto duration = chrono::duration_cast(end-start);
    /*
    for(int i=0;i=chessX*chessY)
        return true;
    while(judge(x,y,cnt)<8)
    {
        Id nextId = next(x,y,cnt);
        finish = run(nextId.x,nextId.y);
        if(finish)
            return true;
        else
            ++cnt;
    }
    //finish==false
    step.pop();
    chess[x][y] = false;
    return false;
}

int judge(int x,int y,int &cnt)
{
    while(cnt<8)
    {
        if(isInChess(x,y,cnt)&&isNeverRun(x,y,cnt))
            return cnt;
        else
            ++cnt;
    }
    return cnt;
}

bool isInChess(int x,int y,int cnt)
{
    assert(cnt<8&&x>=0&&x=0&&y=0);
        case 3:
            return (x+1=0);
        case 4:
            return (x-1>=0)&&(y-2>=0);
        case 5:
            return (x-2>=0)&&(y-1>=0);
        case 6:
            return (x-2>=0)&&(y+1=0)&&(y+2
這麼一個程式碼的框架就算是搭起來了,那麼執行結果呢,在運算8*8的棋盤時,部分點算不出來,就是那種算幾個小時都出不來的情況,有的點可以在幾秒內算出來,如下:
當然這種效率是很捉急的,於是查閱了相關的優化方式,發現可以使用貪婪演算法對其進行優化,加快其執行速度,另一方面,這個原始版本還有其他不足,本就想做些小修改,於是我就立刻寫下了另一個優化版本。 先簡單介紹下貪婪演算法,貪婪演算法就是每一步都選取區域性的最優解,從而最終使得最終解逼近完美解。在本問題下,貪婪演算法的執行效果就是,檢測下一步可能落子的下一步可落子位置數,數量越少的,優先順序越高。比如現在棋子在A點,A點下一步可以落在BCD三個點上,並且B的下一步可能有2個位置,C和D可能有3個位置,那麼B的優先順序就比C和D要高,我們優先選擇B。 程式碼修改如下:
/**
 * 馬踏棋盤優化:
 * 1.DFS,對原有的結構進行改進
 * 2.選擇下一步的方式採取貪婪演算法,即考察每一個備選落子下一步的可落子位置數,
 * 位置數少的優先落子。
 */
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int chessX = 8;
const int chessY = 8;
int chess[chessX][chessY] = {0};
int cnt = 0;//表示深度,表示走的步數
int fx[8] = {1,2,2,1,-1,-2,-2,-1};
int fy[8] = {2,1,-1,-2,-2,-1,1,2};
typedef int Direct;//定義新的資料型別,方便表示,0-8表示8個落子方向
typedef multimap nextMap;//存放每個位置下一個落子優先順序的資料
typedef nextMap::iterator nIter;//定義迭代器型別
bool run(int,int);//遞迴主函式
nextMap judge(int,int,int&,int&);//返回下一個落子位置的優先順序,並且在keyvmin裡存放key值的非0最小值
bool isOk(int,int,Direct);//判斷座標是否可用:1.沒遍歷過2.沒有超出棋盤
int main()
{
    int x,y;
    cout << "請輸入初始座標x:" << "(x>=0&&x<" << chessX << ")" << endl;
    cin >> x;
    assert(x>=0&&x=0&&y<" << chessY << ")" <> y;
    assert(y>=0&&y(end-start);
    for(int i = 0;i=chessX*chessY)
        return true;
    nextMap next = judge(x,y,keyMin,keyMax);
    for (int i = keyMin; i <= keyMax ; ++i)
    {
        if(next.count(i)!=0)
        {
            nIter beg = next.lower_bound(i);
            nIter end = next.upper_bound(i);
            for(nIter j = beg;j!=end;++j)
            {
                finish = run(x+fx[j->second],y+fy[j->second]);
                if(finish)
                    return true;
            }
        }
    }
    chess[x][y] = 0;
    --cnt;
    return false;
}

nextMap judge(int x, int y,int &keyMin,int &keyMax)
{
    nextMap res;
    keyMin = 8;
    keyMax = 0;
    for(int i=0;i<8;++i)
    {
        if(isOk(x,y,i))
        {
            int xx = x + fx[i];
            int yy = y + fy[i];
            int cnt = 0;
            for(int j=0;j<8;++j)
            {
                if(isOk(xx,yy,j))
                    ++cnt;
            }
            res.insert(make_pair(cnt,i));
            if(keyMin>cnt)
                keyMin = cnt;
            if(keyMax= 0 && x < chessX && y >= 0 && y < chessY;
}

效果還是很明顯的,而且全部程式碼採取C++11進行編寫。不過,應該還是可以繼續優化的,只不過目前才疏學淺,就到此為止吧。