Bài tốn ơ số Sudoku

Một phần của tài liệu Tổ chức dữ liệu cho các thuật toán quay lui (Trang 38)

6. Ý nghĩa khoa học của đề tài

2.4.Bài tốn ơ số Sudoku

2.4.1. Giới thiệu bài tốn

Sudoku [4] là một trị chơi trí tuệ nổi tiếng, thu hút nhiều ngƣời tham gia thuộc nhiều tầng lớp, độ tuổi khác nhau. Sudoku ra đời ở Nhật và khơng lâu sau đĩ đã nhanh chĩng lan rộng trên tồn thế giới.

Ngồi khuơn dạng chuẩn cĩ kích thƣớc 9 9 ơ, chia làm 3 3 vùng, cịn cĩ rất nhiều biến thể khác. Một số biến thể phổ biến nhƣ:

Kích thƣớc 4 4 ơ chia làm 2 2 vùng. Kích thƣớc 6 6 ơ chia làm 2 3 vùng.

Kích thƣớc 5 5 ơ chia vùng theo Pentomino pentomino (đƣợc phát hành với tên gọi Logi-5).

Kích thƣớc 7 7 ơ chia vùng theo Heptomino heptomino.

Kích thƣớc 8 8 ơ chia vùng theo qui tắc (4 2):(4 2). Đây là cách chia thành 4 vùng chính, mỗi vùng 16 ơ. Trong mỗi vùng chính lại chia thành 2 vùng 8 8 dựa vào màu nền của từng ơ. Tuy theo cách bố trí các ơ khác màu này, sẽ phát sinh thêm một biến thể con khác. Cách bố trí đơn giản nhất là các ơ khác màu nằm xen kẽ nhau - trơng giống bàn cờ quốc tế.

Biến thể với kích thƣớc lớn cũng khá phổ biến: Kích thƣớc 16 16 ơ (Monster Sudoku).

Kích thƣớc 12 12 ơ chia làm 4 3 vùng (Dodeka Sudoku). Kích thƣớc 25 25 ơ (Giant Sudoku).

Biến thể cĩ kích thƣớc lớn nhất đƣợc phổ biến là 100 100 ơ. Trong phạm vi đề tài, học viên xét đề sudoku cĩ kích thƣớc 9 9.

Một đề sudoku là một hình vuơng, mỗi chiều cĩ 9 ơ nhỏ, hợp thành 9 cột, 9 hàng và đƣợc chia thành 9 khối 3 3. Một vài ơ nhỏ đƣợc đánh số, đĩ là những manh mối để tìm lời giải.

Tùy theo mức độ nhiều hay ít của các manh mối, các câu đố đƣợc xếp loại dễ, trung bình, khĩ hay cực khĩ.

Luật chơi:

Điền vào những ơ trống những con số thích hợp, theo quy luật sau: Các ơ ở mỗi hàng phải cĩ đủ các số từ 1 đến 9, khơng cần theo thứ tự. Các ơ ở mỗi cột phải cĩ đủ các số từ 1 đến 9, khơng cần theo thứ tự. Mỗi khối 3 3, phải cĩ đủ các số từ 1 đến 9.

Thí dụ: 0 1 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 6 0 0 9 0 8 2 5 1 0 0 7 0 4 0 0 0 9 0 5 0 0 0 0 0 3 0 4 0 0 0 2 0 6 0 0 8 2 9 5 0 1 0 0 4 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 7 0 Hình 2.13 Đề bài 1 9 1 2 6 5 4 7 8 3 5 6 3 7 1 2 9 4 6 6 7 4 9 3 8 2 5 1 1 3 7 8 4 6 5 2 9 2 5 6 1 9 7 4 3 8 4 9 8 3 2 5 6 1 7 8 2 9 5 7 1 3 6 4 7 4 1 2 6 3 8 9 5 3 6 5 4 8 9 1 7 2 Hình 2.14 Đáp án đề bài 1

3 0 9 0 0 0 6 0 5 0 0 0 0 0 9 0 0 0 8 0 0 5 0 7 0 0 3 0 6 1 0 3 0 7 0 0 0 0 0 1 0 2 0 0 0 0 0 4 0 5 0 3 1 0 2 0 0 8 0 1 0 0 7 0 0 0 6 0 0 0 0 0 1 0 3 0 0 0 4 0 6 Hình 2.15 Đề bài 2 3 1 9 2 8 4 6 7 5 6 7 5 3 1 9 8 4 2 8 4 2 5 6 7 1 9 3 5 6 1 4 3 8 7 2 9 9 3 8 1 7 2 5 6 4 7 2 4 9 5 6 3 1 8 2 5 6 8 4 1 9 3 7 4 8 7 6 9 3 2 5 1 1 9 3 7 2 5 4 8 6 Hình 2.16 Đáp án đề bài 2 2.4.2. Tổ chức dữ liệu và chương trình

Phương án 1. Quay lui bình thƣờng

Để xác định đƣợc ơ trống ta cần lƣu lại giá trị input vào mảng 2 chiều s. Mảng hai chiều thứ hai là a[si][sj] sẽ chứa kết quả.

Hai biến sisj dùng để ghi nhận chỉ số dịng và cột của ơ hiện hành. Sau khi đọc dữ liệu vào mảng a 9 9 ta lấy ơ trống đầu tiên (chứa số 0). làm điểm xuất phát, rồi lần lƣợt duyệt các ơ trống tiếp theo.

void DiemXuatPhat(){ int i, j; si = sj = 0; for (i = 1; i <= 9; ++i){ if (si > 0) return; for (j = 1; j <= 9; ++j) if (a[i][j] == 0) { si = i; sj = j; return; } } }

Với mỗi ơ trống a[si][sj] ta sử dụng 1 hàm Find() để chỉnh lại giá trị cần điền cho ơ đĩ.

int Find(){

memset(c,0,sizeof(c)); int i, j, di, dj;

// Danh dau cac so tren dong si, cot sj for (i = 1; i <= 9; ++i){

c[a[si][i]] = c[a[i][sj]] = 1; }

// Dau khoi: (di,dj)

di = (si < 4) ? 1 : ((si < 7) ? 4 : 7); dj = (sj < 4) ? 1 : ((sj < 7) ? 4 : 7); for (i = 0; i < 3; ++i)

for (j = 0; j < 3; ++j) c[a[di+i][dj+j]] = 1;

// Tim so de cap nhat o (si,sj) for (i = a[si][sj]+1; i <= 9; ++i) if (c[i] == 0) return i; return 0;

Hàm Find() trƣớc hết đánh dấu các số đã ghi trên dịng si, cột sj và khối 3 3 chứa ơ đang xét (si,sj). Mảng c dùng để đánh dấu đƣợc ghi nhận nhƣ sau: c[i] = 0 cho biết số i chƣa xuất hiện, ngƣợc lại, c[i] > 0 cho biết số i đã xuất hiện trên dịng si hoặc cột sj hoặc trong khối chứa ơ (si,sj).

Nếu giá trị hiện hành của ơ trống này là v thì ta duyệt từ v+1 đến 9 để tìm giá trị đầu tiên c thỏa điều kiện: (adsbygoogle = window.adsbygoogle || []).push({});

Dịng si khơng chứa c; Cột sj khơng chứa c;

Khối chứa ơ (si,sj) khơng chứa c.

Nếu tìm đƣợc giá trị c nhƣ trên, ta điền c vào ơ a[si][sj] và dùng hàm

NextCell() để chuyển qua xử lí ơ trống tiếp theo.

// Tim o trong sau o [si,sj] trong ma tran s void NextCell(){ int i, j; // Duyet dong si for (j = sj+1; j <= 9; ++j) if (s[si][j] == 0) { sj = j; return; }

// Duyet cac dong tu si+1 .. 9 for (i = si+1; i <= 9; ++i) for (j = 1; j <= 9; ++j) if (s[i][j] == 0){ si = i; sj = j; return; } si = sj = 10; }

Nếu khơng tìm đƣợc giá trị c ta dùng hàm PredCell() để lùi lại ơ trống trƣớc đĩ.

// Tim o trong truoc o [si,sj] trong ma tran s void PredCell(){ int i, j; // Duyet dong si for (j = sj-1; j > 0; --j) if (s[si][j] == 0) { sj = j; return; }

// Duyet cac dong tu si+1 .. 9 for (i = si-1; i > 0; --i) for (j = 9; j > 0; --j) if (s[i][j] == 0){ si = i; sj = j; return; } si = sj = 0; }

Thuật tốn kết thúc thành cơng khi mọi ơ trống đều đƣợc điền giá trị hợp lí.

Thuật tốn kết thúc vơ nghiệm nếu ta quay lui về điểm xuất phát sau khi đã duyệt hết mọi khả năng.

Phương án 2. Tổ chức sử dụng con trỏ trƣớc và sau cho mỗi ơ trống

Ta sử dụng thêm một số mảng hai chiều để ghi nhận các ơ trống sát sau và sát trƣớc mỗi ơ trống.

Trƣớc hết ta duyệt xuơi các ơ trống, từ ơ trống đầu tiên đến ơ trống cuối cùng để thiết lập trị cho các mảng iNextjNext. iNext[i][j] chứa số hiệu dịng, jNext[i][j] chứa số hiệu cột của ơ trống sát trƣớc ơ trống (i,j).

Sau đĩ ta duyệt ngƣợc các ơ trống để thiết lập trị cho các mảng iPred

jPred. iPred[i][j] chứa số hiệu dịng, jPred[i][j] chứa số hiệu cột của ơ trống sát sau ơ trống (i,j).

Sau khi thiết lập đƣợc các ơ trống sát sau và sát trƣớc cho mỗi ơ trống ta dùng hàm Find() tìm giá trị cần điền cho các ơ trống.

Nếu tìm đƣợc giá trị thỏa điều kiện, chuyển qua xử lý ơ trống tiếp theo: si = iNext[i][j]; sj = jNext[i][j];

Ngƣợc lại, quay lui về xử lý ơ trống trƣớc đĩ:

si = iPred[i][j]; sj = jPred[i][j];

Thuật tốn kết thúc thành cơng khi mọi ơ trống đều đƣợc điền giá trị hợp lí.

Thuật tốn kết thúc vơ nghiệm nếu ta quay lui về điểm xuất phát sau khi đã duyệt hết mọi khả năng.

2.4.3. Nhận xét

Với phƣơng án 1 quay lui bình thƣờng, sử dụng 2 hàm NextCell và PredCell để tính tốn việc Lùi, Tiến tới các ơ trống trƣớc, sau.

Phƣơng án 2 tổ chức con trỏ trƣớc sau, sử dụng thêm các mảng iNext

jNext, mảng iPredjPred đánh dấu, giảm bớt việc tính tốn, Tiến, Lui nhanh chĩng hơn.

2.5. Kết luận

Trong chƣơng 2, học viên đã trình bày tƣ tƣởng và xây dựng thuật tốn quay lui, tổ chức dữ liệu, áp dụng giải một số bài tốn. Phần cài đặt chƣơng trình và thử nghiệm sẽ đƣợc trình bày ở chƣơng 3.

Chƣơng 3

CÀI ĐẶT CHƢƠNG TRÌNH (adsbygoogle = window.adsbygoogle || []).push({});

Chƣơng này trình bày chƣơng trình cài đặt và chạy thử nghiệm cho các bài tốn ở chƣơng 2.

Các chƣơng trình trong chƣơng 3 đều đƣợc lập trình bằng ngơn ngữ C++

Một phần của tài liệu Tổ chức dữ liệu cho các thuật toán quay lui (Trang 38)