2.2 Thuật toán quay lui (Back Tracking)
2.2.5 Bài toán mã đi tuần
Yêu cầu: Cho một bàn cờ tổng quát dạng nxn, hãy chỉ ra một hành trình của một quân Mã, xuất phát từ một vị trí bắt đầu đi qua tất cả các ô còn lại của bàn cờ, mỗi ô đi đúng một lần.
Ý tưởng cơ bản: dùng thuật toán quay lui; xuất phát từ 1 ô, gọi số nước đi là t=1, ta cho quân mã thử đi tiếp 1 ô (có 8 nước đi có thể), nếu ô đi tiếp này chưa đi qua thì chọn làm bước đi tiếp theo. Tại mỗi nước đi kiểm tra xem tổng số nước đi bằng n*n chưa, nếu bằng thì mã đã đi qua tất cả các ô ⇒ dừng (do chỉ cần tìm một giải pháp).
Trường hợp ngược lại, gọi đệ quy để chọn nước đi tiếp theo. Ngoài ra, nếu tại một bước tìm đường đi, nếu không tìm được đường đi tiếp thì thuật toán sẽ quay lui lại nước đi trước và tìm đường đi khác…
Hình 3.10: Minh họa tám nước đi tiếp của quân mã.
Hình 3.11: Đường đi của quân mã trong bàn cờ 5x5 Cài đặt:
o Cấu trúc dữ liệu:
o Mảng board[MAX][MAX]: lưu bàn cờ, trong đó board[i][j] là ô (i, j); giá trị của board[i][j] là 0 khi quân mã chưa đi qua, và >0 khi quân mã đã đi qua, giá trị board[i][j] lúc này chính là thứ tự nước đi trên hành trình. Thật sự cài đặt mảng board như vậy là đủ, nhưng nếu bổ sung thêm một tí thì sẽ tăng tốc độ thực hiện. Vấn đề bổ sung liên quan đến đường biên, do mỗi lần di chuyển quân mã ta phải kiểm tra xem nước đi có ra ngoài biên hay không.
Ta có thể mở rộng mảng board để không cần phải kiểm tra bằng cách mở rộng hai ô về bốn hướng trên dưới trái phải. Khi đó chỉ cần gán giá trị cho các ô ngoài biên là -1.
Hình 3.12 : Mảng board cho bàn cờ 8x8 ⇒ 12x12.
o Mảng dr[8], dc[8]: lưu các độ dời của bước đi kế tiếp, có tám nước đi có thể cho vị trí quân mã hiện tại. Do đó để đi nước thứ i ta chỉ cần cộng thêm dr[i] cho dòng và dc[i] cho cột!
Hình 3.13: Thứ tự tám nước đi theo chiều kim đồng hồ.
Mảng dr[] = {-2, -1, 1, 2, 2, 1, -1, -2} dc[] = {1, 2, 2, 1, -1, -2, -2, 1} o Thuật giải đệ quy :
Tại mỗi bước lần lượt cho quân mã thử tất cả các nước đi kế tiếp (tám nước đi kế tiếp). Với mỗi bước đi, kiểm tra xem nếu nước đi hợp lệ (chưa đi qua và ở trong
(-2, 1) (-1, 2)
(1, 2) (2, 1) (2, -1)
(1, -2) (-1, -2) (-2, -1)
bàn cờ) thì thử đi nước này. Nếu quân mã đã đi qua hết bàn cờ thì xuất kết quả.
Ngược lại thì gọi đệ quy tiếp cho vị trí mới thử trên. Lưu ý là mỗi khi vị trí đã đi qua được đánh dấu chính bằng chính thứ tự nước đi trên bàn cờ. Sau khi không thử vị trí này thì phải bỏ đánh dấu để chọn giải pháp khác (trường hợp quay lui).
Minh họa hàm Try với step là thứ tự của nước đi, i và j là vị trí của quân mã hiện tại.
Try( int step, int i, j) {
+ Với mỗi nước đi kế tiếp (ii, jj) từ (i, j) + Nếu (ii,jj) hợp lệ
chọn (ii, jj) làm nước đi kế tiếp + nếu đi hết bàn cờ
xuất 1 kết quả + ngược lại
Gọi đệ quy Try(step +1, ii, jj) Không chọn (ii, jj) là nước đi kế tiếp
}
Chương trình C/C++ minh họa cho trường hợp bàn cờ 8x8.
#include "stdafx.h"
#include "conio.h"
#include "stdlib.h"
#define MAX 12 // trường hợp bàn cờ 8x8 void Show(int board[MAX][MAX]);
void Init(int board[MAX][MAX]) {
for(int i=0;i<MAX;i++) for(int j=0;j<MAX;j++)
if(i>=2 && i<=MAX-3 && j>=2 && j<=MAX-3) board[i][j]=0; // đánh dấu chưa đi qua else
board[i][j]=-1; // đánh dấu biên }
void Try(int step, int i, int j, int board[MAX][MAX], int *dr, int *dc) {
for(int k=0; k<7; k++) //duyệt qua các nước đi kế tiếp {
if( board[i+dr[k]][j+dc[k]]==0 ) // nếu vị trí này chưa đi qua {
Board[i+dr[k]][j+dc[k]]= step+1; // đánh dấu chọn vị trí if(step+1==64) //hoàn tất một kết quả
{
Show(board);
printf("Nhan <ENTER> de tiep tuc tim loi \ giai ke. Nhan <ESC> de thoat");
char c;
if(c = getch() == 27) exit(1);
}
else // gọi đệ quy cho nước kế tiếp
Try(step+1, i+dr[k], j+ dc[k], board, dr, dc);
Board[i+dr[k]][j+dc[k]]= 0;// trả tự do cho vị trí vừa chọn }// end if
}//end for }
void Show(int board[MAX][MAX]) {
for(int i=0;i<MAX;i++) {
for(int j=0;j<MAX;j++) printf("%4d",board[i][j]);
printf("\n\n");
} }
void main() {
int board[MAX][MAX];
int dr[8]={-2,-1,1, 2, 2, 1,-1,-2};
int dc[8]={1, 2, 2, 1,-1,-2,-2,-1};
Init(board);
board[2][2]=1; // chọn vị trí đầu tiên Show(board);
Try(1, 2, 2, board, dr, dc);
}
Một kết quả của chương trình như sau:
1 38 43 34 3 36 19 22
44 59 2 37 20 23 4 17
39 42 33 60 35 18 21 10 58 45 40 53 24 11 16 5
41 32 57 46 61 26 9 12
50 47 52 25 54 15 6 27
31 56 49 62 29 8 13 64
48 51 30 55 14 63 28 7 Hình 3.14: Một giải pháp cho bàn cờ 8x8.
Chương 1
Tìm kiếm và Sắp xếp