LỜI MỞ ĐẦU Đề tài Sudoku 9x9 sử dụng ngôn ngữ lập trình C thể hiện các thuật toán đệ quy và quay lui bằng công cụ lập trình DEV C++ để giải một bài toán Sudoku 9x9 cho trước... Bằng việc
Trang 1TRƯỜNG ĐẠI HỌC ĐIỆN LỰC
KHOA CÔNG NGHỆ THÔNG TIN
BÁO CÁO MÔN NHẬP MÔN TRÍ TUỆ NHÂN TẠO
Đ TI:
ÁP DỤNG THUẬT TOÁN QUAY LUI ĐỂ GIẢI BI
TOÁN SUDOKU
Sinh viên thực hiện : ĐỖ VĂN PHONG
NGUYỄN DUY NAM Giảng viên hướng dẫn : NGUYỄN H NAM
Hà Nội, tháng năm
LỜI MỞ ĐẦU
Đề tài Sudoku 9x9 sử dụng ngôn ngữ lập trình C thể hiện các thuật toán đệ quy
và quay lui bằng công cụ lập trình DEV C++ để giải một bài toán Sudoku 9x9 cho trước
Trang 2Thông qua báo cáo này, chúng em xin gửi lời cảm ơn đến thầy Nguyễn Hà Nam – giảng viên hướng dẫn đã hỗ trợ tận tình và giải đáp các thắc mắc trong suốt quá trình làm đồ án Giải thuật và Lập trình
Tuy nhiên, do kiến thức còn hạn hẹp, mặc dù đã nỗ lực hết sức mình, nhưng chắc rằng đồ án khó tránh khỏi thiếu sót Chúng em rất mong nhận được sự thông cảm và chỉ bảo tận tình của quý Thầy cô và các bạn
Xin chân thành và cảm ơn
Trang 3DANH MỤC HÌNH ẢNH
Hình 1 Ví dụ một đề bài Sudoku 9x9.
Hình 2 Đáp án câu đố.
Hình 3 Cây tìm kiếm quay lui.
Hình 4 Mảng S.
Hình 5 Giao diện chương trình chính.
Hình 6 Đầu vào input.txt
Hình 7 Lựa chọn 1: Giải từng bước – Xem tiến trình bước 1 – Đáp án 1 Hình 8 Lựa chọn 1: Giải từng bước - Xem tiến trình bước 2 – Đáp án 2 Hình 9 Lựa chọn 2: Xem đáp án 1
Hình 10 Lựa chọn 2: Xem đáp án 2
Hình 11.So sánh kết quả và đầu ra output.
Trang 41 GIỚI THIỆU Đ TI
Sudoku là một từ Nhật, có thể dịch tạm là con số độc nhất, (Thật ra nó có nguồn gốc từ Mỹ với tên gọi là "đặt con số vào vị trí đúng") Đây 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 có nhiều biến thể khác nhau: 3x3, 4x4, 6x6, 8x8, 9x9, 12x12, 16x16,… Đối với đề tài này, chúng em áp dụng cho sudoku dạng chuẩn 9x9
Bảng câu đố 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 ô lớn 3x3 Một vài ô nhỏ được đánh số, đó là những manh mối duy nhất để bạn tìm lời giải Tuỳ 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ó
Hình 1 Ví dụ một đề bài Sudoku 9x9
Cách chơi Sudoku là điền các số từ 1 đến 9 vào các ô trống theo quy luật đơn giản:
- Các ô ở mỗi hàng (ngang) phải có đủ các con số từ 1 đến 9 không cần theo thứ tự
- Các ô ở mỗi hàng (dọc) phải có đủ các con số từ 1 đến 9 không cần theo thứ tự
- Mỗi miền 3x3 được viền đậm phải có đủ các số từ 1 đến 9
Trang 5Hình 2 Đáp án câu đố
2 CƠ SỞ LÝ THUYẾT
2.1 Ý tưởng
Chương trình giải dựa trên thuật toán quay lui Bằng việc liệt kê các tình huống, thử các khả năng có thể cho đến khi tìm thấy một lời giải đúng, thuật toán quay lui chia nhỏ bài toán, lời giải của bài toán lớn sẽ là kết quả của việc tìm kiếm theo chiều sâu của tập hợp các bài toán phần tử Trong suốt quá trình tìm kiếm nếu gặp phải một hướng nào đó mà biết chắc không thể tìm thấy đáp
án thì quay lại bước trước đó và tìm hướng khác kế tiếp hướng vừa tìm kiếm đó Trong trường hợp không còn một hướng nào khác nữa thì thuật toán kết thúc
2.2 Cơ sở lý thuyết
2.2.1 Thuật toán Quay lui (Backtracking)
Quay lui là một chiến lược tìm kiếm lời giải cho các bài toán thỏa mãn ràng buộc.Các bài toán thỏa mãn ràng buộc là các bài toán có một lời giải đầy đủ, trong đó thứ tự các phần tử không quan trọng Các bài toán này bao gồm một tập các biến mà mỗi biến cần được gán một giá trị tùy theo các ràng buộc cụ thể của bài toán Việc quay lui là thử tất cả các tổ hợp để tìm một lời giải Thế mạnh của phương pháp này là nhiều cài đặt tránh được việc phải thử nhiều tổ hợp chưa hoàn chỉnh, và nhờ đó giảm thời gian chạy, tìm được nhiều đáp án cho những bài toán có nhiều cách giải
Đó là một quá trình tìm kiếm độ sâu trong một tập hợp các lời giải Trong quá trình tìm kiếm, nếu ta gặp một hướng lựa chọn không thỏa mãn, ta quay lui về điểm lựa chọn nơi có các hướng khác và thử hướng lựa chọ tiếp theo Khi đã thử
Trang 6hết các lựa chọn xuất phát từ điểm lựa chọn đó, ta quay lại điểm lựa chọn trước
đó và thử hướng lựa chọn tiếp theo tại đó Quá trình tìm kiếm thất bại khi không còn điểm lựa chọn nào nữa
Quy trình đó thường được cài đặt bằng một hàm đệ quy mà trong đó mỗi thể hiện của hàm lấy thêm một biến và lần lượt gán tất cả các giá trị có thể cho biến
đó, với mỗi lần gán giá trị lại gọi chuỗi đệ quy tiếp theo để thử các biến tiếp theo Chiến lược quay lui tương tự với tìm kiếm theo độ sâu nhưng sử dụng ít không gian bộ nhớ hơn, nó chỉ lưu trữ trạng thái của một lười giải hiện tại và cập nhật nó
Hình 3 Cây tìm kiếm quay lui
-Ở một bài toán hiện tại (mỗi nốt), ta đi tìm lời giả cho bài toán đó Ứng với lờigiải, ta đi giải bài toán kế tiếp cho đến khi bài toán gốc trở nên đầy đủ
-Lời giải của bài toán gốc thường là một lối đi từ gốc đến nốt cuối cùng
3 TỔ CHỨC CẤU TRÚC DỮ LIỆU VÀ THUẬT TOÁN
3.1 Phát biểu bài toán
- Đầu vào : Đề Sudoku đọc bảng từ file , hoặc do người dùng nhập
- Đầu ra (Kết quả): Đáp án có thể là các bước giải Sudoku của chương trình hoặc đáp án toàn bộ chương trình (có thể ra nhiều đáp án nếu đầu vào có nhiều đáp án)
3.2 Cấu trúc dữ liệu
1 Biến
Sử dụng biến kiểu mảng hai chiều để thể hiện vị trí của các phần tử đang xét
Trang 7Mảng hai chiều là một mảng mà các phần tử của nó là mảng một chiều, giống như một bảng gồm có dòng và cột, được đánh dấu vị trí là các chỉ số bao gồm chỉ số dòng và chỉ số cột trong đó
Trong ngôn ngữ C, mảng hai chiều được khai báo theo cú pháp: data_type array_name[num1][num2];
- với data_type là kiểu dữ liệu của các phần tử trong mảng
- num1 số mảng một chiều có trong mảng
- num2 số phần tử có trong mỗi mảng một chiều Ví dụ mảng hai chiều S trong bài được khai báo như sau:
int S[9][9];
Khi đó mảng S giống như một bảng có 9 hàng và 9 cột (mỗi hàng có 9 phần tử),
và mỗi phần tử trong mảng là một số kiểu int
Hình 4 Mảng S
Muốn truy cập đến một phần tử trong mảng ta dùng cú pháp S[i][j] Khi đó S[i] [j] chứa giá trị của ô ở vị trí hàng i cột j
2 Hàm
Trong mảng hai chiều đưa vào chương trình, các phần tử trong mảng tương ứng với các ô trong đề Sudoku Trong mảng này, các ô chưa được điền nhận giá trị bằng 0, các ô đã được điền mang giá trị đã được điền vào Với mỗi ô chưa được điền, giá trị của ô đó bằng 0
Khi đó để kiểm tra khả năng điền giá trị của ô đó ta dùng hàm check() như sau:
Trang 8- Kiểm tra hàng: Kiểm tra giá trị k (với k có thể nhận giá trị từ 1-9) có thể điền vào ô trên hay không Kiểm tra xem trên hàng đó đã có ô nào có giá trị bằng k hay chưa bằng cách duyệt hết tất cả các phần tử trên hàng đó, nếu đã có thì loại bỏ khả năng ô đó nhận giá trị k
- Kiểm tra cột: Kiểm tra giá trị k (với k có thể nhận giá trị từ 1-9) có thể điền vào ô đó không Kiểm tra xem trên cột đó đã có ô nào mang giá trị k chưa bằng cách duyệt hết tất cả các phần tử có trong cột, nếu đã có thì loại
bỏ khả năng ô đó nhận giá trị k
- Kiểm tra trong vùng 9 ô: với giá trị k có thể có giá trị từ 1 đến 9 Kiểm tra xem trong vùng 9 ô có ô nào chứa giá trị k chưa, nếu có thì loại trừ khả năng nhận giá trị k của ô đó
- Nếu với một số k sau khi kiểm tra đạt cả 3 khả năng trên thì có thể gán giá trị k cho ô đó
Trong đó các tham số truyền vào là:
+ Mảng S[][] là mảng chúng ta đang xét
+ Số nguyên x chứa vị trí dòng của ô đang xét trong mảng + Số nguyên y chứa vị trí cột của ô đang xét
+ k là giá trị muốn kiểm tra xem có thể điền vào ô đang xét hay không
Hàm trên sẽ trả về giá trị 1 nếu k được điền vào ô đang xét và trả về 0 nếu không được điền vào ô đang xét
Hàm sovle() thực hiện thuật toán quay lui Hàm có 3 tham số đầu vào: mảng dữ liệu S, biến vị trí x,y Chương trình sẽ duyệt trên từng ô của mảng hai chiều, trên từng ô chương trình sẽ kiểm tra xem ô đó đã được điền hay chưa, nếu chưa được điền thì chương trình sẽ duyệt hết tất cả các giá trị k chạy từ 1- 9 xem những giá trị nào có thể điền vào ô đang xét Tại đây ứng với mỗi giá trị có thể điền vào ô đang xét chương trình sẽ gọi đệ quy đến ô tiếp theo trong hàng Nếu trường hợp
do giá trị điền ô trước đó mà ô tiếp theo không thể điền giá trị thì chương trình
sẽ quay lui lại và thử với giá trị khác Cứ như thế cho đến khi xét xong ô cuối cùng trong mảng (ô thứ 81) Tại đây chương trình sẽ xuất ra kết quả và kết thúc chương trình
Trang 93.3 Thuật toán
3.3.1 Thuật toán quay lui
Thuật toán quay lui (backtracking) như tên gọi của nó, là một quá trình tìm kiếm
mà trong quá trình tìm kiếm, nếu ta gặp một hướng lựa chọn không thỏa mãn, ta quay lui về điểm lựa chọn nơi có các hướng khác và thừ hướng lựa chọn tiếp theo Quá trình tìm kiếm thất bại khi không còn điểm lựa chọn nào nữa
Độ phức tạp:
Trong trường hợp xấu nhất độ phức tạp của quay lui vẫn là cấp số mũ Vì
nó mắc phải các nhược điểm sau:
• Rơi vào tình trạng "thrashing": quá trình tìm kiếm gặp phải bế tắc với cùng một nguyên nhân
• Thực hiện các công việc dư thừa: Mỗi lần chúng ta quay lui, chúng ta cần phải đánh giá lại lời giải trong khi đôi lúc điều đó không cần thiết
• Không sớm phát hiện được các khả năng bị bế tắc trong tương lai Quay lui chuẩn, không có cơ chế nhìn về tương lai để nhận biết được nhánh tìm kiếm sẽ đi vào bế tắc
4.CHƯƠNG TRÌNH VÀ KẾT QUẢ
4.1 Tổ chức chương trình
#include <time.h>
#include <stdio.h>
#include <window
s.h> int c=0;
char N[10] = { '_' '1' '2' '3' '4' '5' '6' '7' '8' '9' , , , , , , , , , };
Trang 10int S[9][9];
show(
void int S [][9]);
gotoxy( ,
void int x int y );
setcolor( );
void int color
void tientrinh( , int int int int i , j k , color );
readFile(FILE * ); int f
void getData( int S [][9]);
option1_solve_sudoku( [9][9], , );
void option2_solve_sudoku( int S [9][9], int x , int y );
int check( int S [][9], int x , int y , int k );
Output(
void int S [][9]);
resuilt(
void int S [][9]);
main( ){ getData(S); index; t=1; show(S); printf( );
while (t)
{ printf( " -SUDOKU -\n" ); printf( "1 Xem tien trinh : \n" ); printf( "2 Xem dap an : \n" ); printf( " -\n" );
printf( "Nhap lua chon:" ); scanf( "%d" ,&index);
switch (index)
{
case 1: option1_solve_sudoku(S,0,0); break case ; 2:
option2_solve_sudoku(S,0,0); c=0;
break case ; 0: t=0; break ;
default :
break;
}
}
Trang 11// ham gotoxy
void gotoxy( , ) int x int y
{ static HANDLE h = NULL;
if(!h)
h = GetStdHandle(STD_OUTPUT_HANDLE);
COORD c = { , }; x y
SetConsoleCursorPosition(h,c);
}
// ham thiet lap mau chu
void setcolor( int color )
{
HANDLE hConsoleColor; hConsoleColor =
GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsoleColor, color );
}
// ham tien trinh
void tientrinh( , int int int i , j k , int color )
{ size= 4; gotoxy( *size, int j i *(size-2));
setcolor( color ); printf( "%c" , N[ ]); } k
// ham doc file
int readFile(FILE * ){ n; fscanf( f int f , "%d" , &n);
return n;
}
// ham chuyen du lieu file qua mang S
void getData( int S [][9])
{ A[81]; count = 0; FILE *f; f = fopen( int int "Input.txt" "r" , ); while (!feof(f)){ A[count] = readFile(f); count++; } t = 0; i, j; int int for (i = 0; i < 9; i++)
{ for (j = 0; j < 9; j++) {
S [i][j] = A[t]; t++;
} } fclose(f);
Trang 12// lua chon tien trinh giai sudoku
void option1_solve_sudoku( [9][9], , ){ Sleep(400); int S int x int y
== 9){ == 8){
setcolor(12); gotoxy(60, c*20+1); printf( "DAP AN
%d:\n\n" ,c+1); resuilt( ); system( S "pause" ); Output( ); c+ S +;
} else { option1_solve_sudoku( , S x +1,0);
}
} else if( S [ x y ][ ] == 0){ k = 0; int for (k = 1; k <=9; k++){
if(check( , , S x y ,k)){ tientrinh( x y , k,12);
S x y [ ][ ] = k; option1_solve_sudoku( , , +1); S x y tientrinh( x y , 0, 5); S x y [ ][ ] = 0;
Sleep(400);
} }
} else { option1_solve_sudoku( S x y , , +1);
}
}
// lua chon xuat tat ca dap an ra man hinh
void option2_solve_sudoku( int S [9][9], int x , int y ){
== 9){ == 8){ setcolor(12); gotoxy(60, c*20+1); printf(
%d:\n\n" ,c+1); resuilt( S );
Output( S ); c++;
} else { option2_solve_sudoku( , S x +1,0);
}
} else if( S [ x y ][ ] == 0){ k = 0; int for (k = 1; k <=9; k++){
if(check( , , ,k)){ S x y
Trang 13S x y [ ][ ] = k; option2_solve_sudoku( , , +1); S x y
S x y [ ][ ] = 0;
} }
} else { option2_solve_sudoku( S x y , , +1);
}
}
// ham kiem tra so k co dien duoc vao vi tri x y hay ko
int check( int S [][9], int x , int y , int k ){
i = 0, j = 0; int for (i = 0; i <9 ; i++){ if( S x [ ][i] == ) k return 0; } for (i = 0; i <9 ; i++){ if( S [i][ y ] == ) k return 0;
} a = /3, b = /3; int x y for (i = 3*a; i < 3*a+3; i++){ for (j = 3*b; j < 3*b+3; j++){ if( S [i][j] == ) k return 0;
}
} return 1;
} // ham xuat mang
void show( int S [][9]){ setcolor(10);
i = 0, j = 0; size= 4; int int for ( i= 0; i<9 ; i++)
{
for (j= 0; j<9; j++)
{
gotoxy(j*size,i*(size-2)); printf( "%c" ,N[ [i][j]]); S }
}
}
//ham xuat dap an ra man hinh
void resuilt( [][9]){ setcolor(3); int S
i = 0, j = 0; size= 4; int int for ( i= 0; i<9 ; i++)
{
Trang 14for (j= 0; j<9; j++)
{
gotoxy(j*size+60,i*(size-2)+3+c*20); printf( "%c" ,N[ [i] S [j]]);
}
}
gotoxy(0, 21); printf( "\n" );
}
// ham xuat ra file dap an
void Output( int S [][9]){
FILE *p = fopen( "Output.TXT" "w+" , ); int
i, j;
for (i = 0; i < 9; i++)
{ for (j = 0; j < 9; j++)
{ a = [i][j]; fprintf(p, int S "%2d" ,
S [i][j]);
} fprintf(p, "\n" );
} fclose(p);
system( "pause" );
}
4.2 Ngôn ngữ cài đặt
Chương trình sử dụng ngôn ngữ C
4.3 Kết quả
4.3.1 Giao diện chính của chương trình
Đây là màn hình hiển thị các lựa chọn khi chạy chương trình sudoku 9x9
1 Lựa chọn 1: Xem các bước giải sudoku
2 Lựa chọn 2: Xem đáp án
Trang 15Hình 5 Giao diện chương trình chính
4.3.2.
Hình 6 Đầu vào Input.txt
4.3.3 Kết quả thực thi của chương trình
Trang 16Hình 7 Lựa chọn 1: Giải từng bước – Xem tiến trình bước 1 – Đáp án 1
Hình 8 Lựa chọn 1: Giải từng bước - Xem tiến trình bước 2 – Đáp án 2
Trang 17Hình 9 Lựa chọn 2: Xem đáp án 1
Hình 10 Lựa chọn 2: Xem đáp án 2
Trang 18Hình 11 So sánh kết quả và đầu ra output
4.3.4 Nhận xét
Chương trình giải được ô số sudoku có kích thước 9x9 có mức độ từ dễ đến khó Hạn chế của chương trình là chỉ đưa ra được một đáp án và nếu dữ liệu đầu vào quá ít thì chương trình có thề không đưa ra được kết quả Bài toán sudoku là bài toán phức tạp nên nhóm chỉ thao tác với chế độ văn bản
5 KẾT LUẬN VÀ HƯỚNG PHÁT TRIỂN
5.1 Kết luận
- Ưu điểm:
+ Cài đặt được thuật toán quay lui để giải bài toán
+ Chương trình đạt được yêu cầu đề ra (giải bài toán sudoku 9x9)
- Nhược điểm:
+ Chỉ giải được Sudoku 9x9, chưa mở rộng được bài toán với các dạng các nhau
5.2 Hướng phát triển
Sudoku ngày nay có rất nhiều biến thể, vì vậy cần phải phát triển chương trình theo hướng có thể giải nhiều dạng sudoku khác nhau, áp dụng nhiều thuật toán nhằm giải tối ưu hơn