骑士旅行问题的试探性算法研究
背景介紹
騎士旅行問題想必大家都很熟悉了吧
這道題的眾人向做法就是深度搜索(直接給出一個很C_T風(fēng)格的代碼):
#include<iostream> #include<iomanip>using namespace std;int zz[8][2]={{1,2},{2,1},{1,-2},{-2,1},{-1,2},{2,-1},{-1,-2},{-2,-1}}; int a[8][8]={0},tot=0,_x,_y;void print() {cout<<++tot<<endl;for (int i=0;i<8;i++) { for (int j=0;j<8;j++) cout<<setw(3)<<a[i][j];cout<<endl;}return; }void doit(int x,int y,int t) {if (t>64) {print();return;}for (int i=0;i<8;i++) {int xx=x+zz[i][0];int yy=y+zz[i][1];if ((xx>=8)||(xx<0)||(yy>=8)||(yy<0)||(a[xx][yy])) continue;a[xx][yy]=t;doit(xx,yy,t+1);a[xx][yy]=0;} }int main() {cout<<"Enter the initial position: ";cin>>_x>>_y;a[_x][_y]=1;doit(_x,_y,2);system("pause");return 0; }下面的代碼找到一個可行方案就停止搜索(但是在得到可行解之后的回溯過程意外的有些慢):
#include<iostream> #include<iomanip>using namespace std;int zz[8][2]={{1,2},{2,1},{1,-2},{-2,1},{-1,2},{2,-1},{-1,-2},{-2,-1}}; int a[8][8]={0},tot=0,_x,_y; bool flag=1;void print() {for (int i=0;i<8;i++) { for (int j=0;j<8;j++) cout<<setw(3)<<a[i][j];cout<<endl;}return; }void doit(int x,int y,int t) {if (t>64) {flag=0;print();return;}for (int i=0;i<8&&flag;i++) {int xx=x+zz[i][0];int yy=y+zz[i][1];if ((xx>=8)||(xx<0)||(yy>=8)||(yy<0)||(a[xx][yy])||(!flag)) continue;a[xx][yy]=t;doit(xx,yy,t+1);a[xx][yy]=0;} }int main() {cout<<"Enter the initial position: ";cin>>_x>>_y;a[_x][_y]=1;doit(_x,_y,2);system("pause");return 0; }總的來說,這是一種很暴力的算法,相當(dāng)于無優(yōu)先級窮舉所有可能路徑
這就會帶來一個問題:有些起始位置以當(dāng)前的窮舉順序是無法在有限時間內(nèi)得到可行解的
以前小憨憨沒有考慮過這個問題,直到C先生不斷睿智發(fā)問,我才意識到這幾年我一直停留在眾人向做法毫無進步
所以今天,C_T要帶來一種更加優(yōu)秀的做法!
透試算法研究
現(xiàn)在我們希望開發(fā)一個移動騎士的試探策略
試探不一定成功,但認(rèn)真設(shè)計的試探方法能提高成功的機會
顯然,外層格子比在棋盤中央的殼子更難移動,實際上,最難訪問的是四角的格子
這里所謂的 ” 難訪問 “ ,實際上就是相比較其他格子,有更少的格子能夠到達(dá)該處
反過來說,就是從這個格子出發(fā)走一步能夠到達(dá)的格子數(shù)更少
嗯?不太明白嗎?
下面給出一個棋盤,每一格上標(biāo)記能訪問的格數(shù)(盲猜有助于理解)
我叫ta前進策略圖(好正常的名字,不像我的一貫作風(fēng)),格子上的值我就叫ta前進可能值:
憑直覺,應(yīng)先將騎士移動到最難到達(dá)的格子,在旅行即將結(jié)束時再訪問最容易到達(dá)的格子,這樣成功的機率較高
意識流一下就可以發(fā)現(xiàn),騎士起始時的路徑選擇應(yīng)該盡可能少一點
因為在后面試探失敗進行回溯的時候,如果起始時的路徑選擇較多,回溯引發(fā)的搜索時間代價會大幅上升
我們可以開發(fā)一個訪問性試探,將每格進行訪問性分類,然后總是設(shè)法把騎士移到最難訪問的點
一句話總結(jié):任何時候,總是設(shè)法將騎士移動到最難訪問的點
從這句話中,我們就可以看出這個策略要維護的重點:
每走一步,我們就需要重新維護一遍前進策略圖
并且每次在枚舉策略的時候,按照該點周圍前進可能值由小到大的順序進行枚舉
我在說些什么泡泡茶壺!!!!還是畫圖告訴大家吧!
然后可能就會有人問了,這三個 " 8 " 有沒有什么先后順序呢?
實際上應(yīng)該可以有的,畢竟一號8和三號8比二號8更靠近邊緣,選擇一號8或者三號8,對于之后的選擇可能更加有利,
但是如果這樣區(qū)別的話,就牽扯到處理下一階段的前進策略圖了
整個問題的難度就會大幅上升,甚至與之前我們想要降低復(fù)雜度的初衷背道而馳,所以我們在這里初步簡單的認(rèn)為,這三個8先選擇哪個都沒有區(qū)別
那么我們的編程思路終于浮出水面了!!!!
在搜索函數(shù)中,
首先依據(jù)當(dāng)前的前進策略圖得到 ( x , y ) 處的最優(yōu)前進策略
按照最有前進策略前進后,維護最新的前進策略圖,之后進入下一層搜索
在確定這個算法時,我經(jīng)歷了痛苦的思維地獄:到底應(yīng)該先維護前進策略圖還是先找到最優(yōu)前進策略?
顯然,如果一個位置已訪問,那么該點的前進可能值就可以視為正無窮(優(yōu)先級最低)
但是我們還要保證能成功回溯,所以不能在初始數(shù)組上直接修改,需要復(fù)制地圖
如果我頭鐵直接在原數(shù)組上修改,并且取消回溯過程,就相當(dāng)于認(rèn)定了在這種類似貪心的算法下一定能找到可行解,不過我怎么證明這種算法的解必存在性呢?
就算我們姑且拋開這個問題不考慮(默認(rèn)正確)
在搜索過程中,先維護前進策略圖還是先找到最優(yōu)前進策略實際上是毫無區(qū)別的
但是因為我們初始點是直接進入搜索的,這個初始點的加入實際上打破了之前的前進策略圖的
嚴(yán)謹(jǐn)起見,我們還是應(yīng)該先維護前進策略圖
而且維護前進策略圖和獲得最優(yōu)策略的操作完全分開會比較清晰一點
但是為了壓縮常數(shù),實際上這兩種操作可以雜糅一下
TIP
啊啊啊啊啊啊啊啊,我好蠢啊
整個思路的完善C先生幫了我很大的忙,比心心~
一開始我還以為每一次都要全棋盤維護前進策略圖,然后C先生告訴我說每走一步只會影響到ta周圍的可到達(dá)點
枚舉策略順序也不用全部抽出來排序,只要找到當(dāng)前情況下的最優(yōu)方案就可以了
(我真的是學(xué)了很多年競賽的OIer嘛,被踩了啊TwT)
下面給出的是舍棄回溯的不完善代碼,但是在博主的實踐下,發(fā)現(xiàn)除了 ( 3 , 4 ) ( 4 , 3,) 點,其他點都可以靠這種 “ 貪心 ” 一次成功
#include<iostream> #include<iomanip>using namespace std;const int INF=0x33333333; int zz[8][2]={{1,2},{2,1},{1,-2},{-2,1},{-1,2},{2,-1},{-1,-2},{-2,-1}}; int strategy_map[8][8]={{2,3,4,4,4,4,3,2},{3,4,6,6,6,6,4,3},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{3,4,6,6,6,6,4,3},{2,3,4,4,4,4,3,2}}; int ans[8][8]={0},_x,_y; bool flag=1;void print() {for (int i=0;i<8;i++) { for (int j=0;j<8;j++) cout<<setw(3)<<ans[i][j];cout<<endl;}return; }int update(int x,int y) {int mn=8,pos=9;strategy_map[x][y]=INF;for (int i=0;i<8;i++) {int xx=x+zz[i][0];int yy=y+zz[i][1];if ((xx<0)||(xx>=8)||(yy<0)||(yy>=8)||ans[xx][yy]) continue;strategy_map[xx][yy]--;if (strategy_map[xx][yy]<mn) mn=strategy_map[xx][yy],pos=i;}return pos; }void doit_try(int x,int y,int t) {if (t>64) {flag=0;print();return;}int num=update(x,y);if (num==9) return;int xx=x+zz[num][0];int yy=y+zz[num][1];ans[xx][yy]=t;doit_try(xx,yy,t+1); //省略回溯return; }int main() {cout<<"Enter the initial position: ";cin>>_x>>_y;ans[_x][_y]=1;doit_try(_x,_y,2);system("pause");return 0; }之后我和C先生又花了將近兩天的時間研發(fā)出了回溯算法
主要還是C先生的功勞啦,謝謝大大對我不離不棄TwT
這里對于我們的思路再羅嗦兩句~
回溯問題的難點就在于前進策略圖的維護
我一開始的思路被INF的設(shè)定堵塞了
實際上,可以通過ans數(shù)組直接把這些點踢出討論組
編輯一個update數(shù)組搞定更新地圖的任務(wù)
之后我們可以再增加一個數(shù)組,記錄8個方向的可通行性
這樣在回溯get新的方向的時候,就可以排除掉之前的方案,選擇剩下方案中的最優(yōu)解
獻(xiàn)!丑!了!
#include<iostream> #include<iomanip>using namespace std;const int INF=0x33333333; int strategy_map[8][8]={{2,3,4,4,4,4,3,2},{3,4,6,6,6,6,4,3},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{4,6,8,8,8,8,6,4},{3,4,6,6,6,6,4,3},{2,3,4,4,4,4,3,2}}; int zz[8][2]={{1,2},{2,1},{1,-2},{-2,1},{-1,2},{2,-1},{-1,-2},{-2,-1}}; int ans[8][8]={0},_x,_y,tot;void print() {for (int i=0;i<8;i++) { for (int j=0;j<8;j++) cout<<setw(3)<<ans[i][j];cout<<endl;}cout<<endl;return; }void update(int x,int y,int z) {for (int i=0;i<8;i++) {int xx=x+zz[i][0];int yy=y+zz[i][1];if ((xx<0)||(xx>=8)||(yy<0)||(yy>=8)) continue;strategy_map[xx][yy]+=z;}return; }int get_num(int x,int y,bool acc[]) {int mn=8,pos=9;for (int i=0;i<8;i++) {if (!acc[i]) continue;int xx=x+zz[i][0];int yy=y+zz[i][1];if ((xx<0)||(xx>=8)||(yy<0)||(yy>=8)||ans[xx][yy]) continue;//注意這里的前提條件有ans[xx][yy]==0if (strategy_map[xx][yy]<mn) mn=strategy_map[xx][yy],pos=i;}return pos; }void doit_try(int x,int y,int t) {if (t>64) {tot--;print();return;}bool accessible[8]; //方向可通行性memset(accessible,1,sizeof(accessible));update(x,y,-1); //更新地圖int num=get_num(x,y,accessible); //get最優(yōu)策略while (num!=9) {int xx=x+zz[num][0];int yy=y+zz[num][1];accessible[num]=0; //之后即使發(fā)生回溯也不可能選擇這個方向了ans[xx][yy]=t;doit_try(xx,yy,t+1);if (!tot) return;ans[xx][yy]=0;num=get_num(x,y,accessible); //get最優(yōu)策略}update(x,y,1); //恢復(fù)地圖進行回溯return; }int main() {cout<<"Enter the initial position: ";cin>>_x>>_y;cout<<"Methods number: ";cin>>tot;ans[_x][_y]=1;doit_try(_x,_y,2);system("pause");return 0; }總結(jié)
以上是生活随笔為你收集整理的骑士旅行问题的试探性算法研究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英语天天读】Cultivating a
- 下一篇: label标签的使用