馬踏棋盤C++實現與貪婪演算法優化
阿新 • • 發佈:2019-01-27
馬踏棋盤問題即在一個8*8的棋盤上,指定一個起點,找到一個路徑,讓棋子按照馬走日的規則將所有格子無重複的遍歷一次。
這個問題是在學習資料結構與演算法的時候看到的,當時看的是C語言的版本,也沒記住具體的解法,事後回顧起來覺得很有意思,於是自己用C++編寫了一段程式碼嘗試一下。
首先進行分析:從資料結構的角度來分析,一個棋盤可以看作一個二維陣列。然後是對每次落子進行分析,根據馬走日的規則,假設不考慮是否會出現越界情況,則在某位置X上,下一步落子共有八種可能性,如圖所示,按照順時針順序依次標記為0,1,2,3,4,5,6,7:
- | 7 | - | 0 | - |
6 | - | - | - | 1 |
- | - | X | - | - |
5 | - | - | - | 2 |
- | 4 | - | 3 | - |
那麼顯然易見,已知X的情況下,利用深度優先遍歷的方式,是一定可以得到一個路徑滿足要求的,只不過這麼做的計算複雜度非常之高。有了想法,當然是以能跑出結果為導向先寫出大概的程式碼才行,於是我按照這個思路寫出了原始版本:
這麼一個程式碼的框架就算是搭起來了,那麼執行結果呢,在運算8*8的棋盤時,部分點算不出來,就是那種算幾個小時都出不來的情況,有的點可以在幾秒內算出來,如下:/* * 馬踏棋盤最原始的深度優先演算法 * 利用棧 */ #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
當然這種效率是很捉急的,於是查閱了相關的優化方式,發現可以使用貪婪演算法對其進行優化,加快其執行速度,另一方面,這個原始版本還有其他不足,本就想做些小修改,於是我就立刻寫下了另一個優化版本。 先簡單介紹下貪婪演算法,貪婪演算法就是每一步都選取區域性的最優解,從而最終使得最終解逼近完美解。在本問題下,貪婪演算法的執行效果就是,檢測下一步可能落子的下一步可落子位置數,數量越少的,優先順序越高。比如現在棋子在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進行編寫。不過,應該還是可以繼續優化的,只不過目前才疏學淺,就到此為止吧。