馬踏棋盤演算法(騎士周遊問題)
阿新 • • 發佈:2019-01-23
一、馬踏棋盤演算法
1、國際象棋的棋盤為8*8的方格棋盤,將“馬”放在任意指定的方格中,按照“馬”走棋的規則將“馬”進行移動。要求每個方格只能進入一次,最終使得“馬”走遍棋盤64個方格。
2、關於國際象棋“馬”的走法:以“日”字為單位往斜對角上的那個方格走。如下圖所示馬在當前位置就有8種走法,如果不考慮邊緣的特殊情況,那麼需要8^64的時間複雜度才可以把整個棋盤的一個正確路徑找出來。
3、對於在n*n的棋盤上,當n>=5且為偶數的時候,以任意點作點都有解。
4、實現程式碼如下:
5、從實現程式碼上來看,該演算法運用到了遞迴法、回溯法以及圖的深度遍歷思想,不斷的遞迴判斷,失敗再回退,直到成功遍歷或者失敗為止。不過實現方式還有很大的可優化空間。執行結果如下,需要化很長一段時間來執行,不過起始點的選擇很重要,有些時候選擇了其他的起始點,則有可能很久很久都計算不出來。/************************************************************/ /** 馬踏棋盤演算法(騎士周遊問題) **/ /************************************************************/ #include <stdio.h> #include <time.h> #define X 8 #define Y 8 int chess[X][Y]; /** * 列印棋盤,棋盤中每個格子的數值就是遍歷的次序 */ void Print() { int i, j; for(i = 0; i < X; i++) { for(j = 0; j < Y; j++) { printf("%2d\t", chess[i][j]); } printf("\n"); } printf("\n"); } /** * 找到基於馬當前在棋盤中的位置的(x,y)座標的下一個可走的位置的座標,如果成功找到則返回1,且直接修改原來位置的座標; * 否則返回0 * 成功找到的條件是該位置存在且還沒有被馬走過 * @param x:當前馬所在棋盤位置的x座標 * @param y:當前馬所在棋盤位置的y座標 * @param count:不考慮邊緣的情況,馬在任意一位置的下一個位置都可能有八種情況,count就是對這八種情況進行判斷 只要找到其中一種就返回1 */ int NextXY(int *x, int *y, int count) { switch(count) { case 1: if(*x + 2 <= X - 1 && *y - 1 >= 0 && chess[*x + 2][*y - 1] == 0) { *x += 2; *y -= 1; return 1; } break; case 2: if(*x + 2 <= X - 1 && *y + 1 <= Y - 1 && chess[*x + 2][*y + 1] == 0) { *x += 2; *y += 1; return 1; } break; case 3: if(*x + 1 <= X - 1 && *y - 2 >= 0 && chess[*x + 1][*y - 2] == 0) { *x += 1; *y -= 2; return 1; } break; case 4: if(*x + 1 <= X - 1 && *y + 2 <= Y - 1 && chess[*x + 1][*y + 2] == 0) { *x += 1; *y += 2; return 1; } break; case 5: if(*x - 2 >= 0 && *y - 1 >= 0 && chess[*x - 2][*y - 1] == 0) { *x -= 2; *y -= 1; return 1; } break; case 6: if(*x - 2 >= 0 && *y + 1 <= Y - 1 && chess[*x - 2][*y + 1] == 0) { *x -= 2; *y += 1; return 1; } break; case 7: if(*x - 1 >= 0 && *y - 2 >= 0 && chess[*x - 1][*y - 2] == 0) { *x -= 1; *y -= 2; return 1; } break; case 8: if(*x - 1 >= 0 && *y + 2 <= Y - 1 && chess[*x - 1][*y + 2] == 0) { *x -= 1; *y += 2; return 1; } break; default: break; } return 0; } /** * 深度優先遍歷棋盤 * @param x:當前馬即將要走的位置的x座標 * @param y:當前馬即將要走的位置的y座標 * @param step:當前馬即將要走的這個位置是第step步,也就是說馬已經走過了step-1個棋盤 */ int TraversalChessBoard(int x, int y, int step) { // 定義x1和y1變數儲存馬即將要走的這個位置的座標 int x1 = x, y1 = y; // flag儲存是否有下一個可走的位置;count用於迴圈判斷八種可走的位置 int flag = 0, count = 1; // 標記該位置已經走過了 chess[x][y] = step; // 如果此時step等於棋盤格子的個數,則說明已經把棋盤的每一個格子都走過一次了 // 則列印棋盤輸出走過的順序;這也是下面的遞迴的返回條件吧 if(step == X * Y) { Print(); return 1; } // 如果還沒有走完棋盤,則選擇下一個位置,依次判斷那八種可能的情況 flag = NextXY(&x1, &y1, count); while(flag == 0 && count < 8) { count++; flag = NextXY(&x1, &y1, count); } // 在當前位置找到下一個可走的位置 while(flag) { // 遞迴呼叫走向下一個位置,因為在NextXY函式中直接修改了當前位置的座標,所以此時的x1和y1就表示下一個可走位置的座標 // 如果此時返回的是1,說明棋盤已經走完了,繼續向上返回1 if(TraversalChessBoard(x1, y1, step + 1)) { return 1; } // 返回的不是1,則返回上一個位置,在上一個位置重新選擇一個可走位置繼續 x1 = x; y1 = y; // 前count種可走位置的情況都判斷過了,從count+1種情況繼續判斷 count++; flag = NextXY(&x1, &y1, count); while(flag == 0 && count < 8) { count++; flag = NextXY(&x1, &y1, count); } } // 八種可能的情況都判斷過了,還是沒有下一個可走的位置了 // 那麼說明該次深度遍歷失敗,此種走法不行,則應該從當前位置回退到上一個位置 // 所以重新標記當前位置還沒有被走過 if(0 == flag) { chess[x][y] = 0; } // 如果都沒有可走的位置了,則返回0,代表繼續向後回退 return 0; } int main() { int i, j; clock_t start, finish; start = clock(); for(i = 0; i < X; i++) { for(j = 0; j < Y; j++) { chess[i][j] = 0; // 初始化棋盤 } } // 從座標(2,0)開始走 if(!TraversalChessBoard(2, 0, 1)) { printf("遍歷棋盤失敗......\n"); } finish = clock(); printf("本次遍歷共耗時:%f秒\n", (double)(finish - start)/CLOCKS_PER_SEC); return 0; }
6、其他相關概念
哈密爾頓路徑:圖G中的哈密爾頓路徑指的是經過圖G中每個頂點,且只經過一次的一條軌跡。如果這條軌跡是一條閉合的路徑(從起點出發不重複地遍歷所有點後仍能回到起始點),那麼這條路徑稱為哈密爾頓迴路。沒有閉合的叫通路或路徑。