Tuy nhiên có những trường hợp việc tìm ra một thuật toán với những tính chất đòi hỏi như trên rất khó khăn nhưng có cách giải có thể vi phạm các tính chất của thuật toán nhưng lại khá đơ
Trang 1TRƯỜNG ĐẠI HỌC BÁCH KHOA
KHOA CÔNG NGHỆ THÔNG TIN
- -BÁO CÁO
ĐỒ ÁN MÔN HỌC
CẤU TRÚC DỮ LIỆU VÀ
THUẬT TOÁN
ĐỀ 38 : Trên bàn cờ n x n hãy xếp 2n con hậu sao cho mỗi hàng và mỗi
cột có đúng 2 con hậu
Giáo viên hướng dẫn : Phan Thanh Tao
Sinh viên thực hiện : Phan Văn Tuấn
Nguyễn Trung Dũng
Phùng Duy Thiện Trương Công Hưng
Lớp : 07T1
Nhóm : 12A
Đà Nẵng, 12/2009
Trang 2MỤC LỤC
MỤC LỤC 2
BÁO CÁO ĐỒ ÁN 3
Phần I Cơ sở lý thuyết 3
I.1 Khái niệm về đệ quy 3
I.2 Định nghĩa theo đệ quy 4
I.3 Phân loại kĩ thuật đệ quy: 4
I.4 Cấu trúc của thuật toán đệ quy 4
I.5 Kết luận: 5
Phần II Phân tích thuật toán và chương trình: 5
II.1 Cấu trúc dữ liệu: 5
II.2 Thuật toán giải quyết: 6
II.3 Chương trình: 6
II.4 Giải thích chương trình: 10
II.5 Kết quả chương trình: 12
Phần III Kết luận: 15
Trang 3BÁO CÁO ĐỒ ÁN
CẤU TRÚC DỮ LIỆU VÀ THUẬT TOÁN
Nội dung đề tài:
Trên bàn cờ vua kích thước n x n, hãy xếp 2n con hậu sao trên mỗi hàng và mỗi cột có đúng 2 con hậu
Phần I Cơ sở lý thuyết
I.1 Khái niệm về đệ quy:
Thuật toán đệ qui là một trong những sự mở rộng của khái niệm thuật toán Như đã biết, một thuật toán được đòi hỏi phải thỏa mãn các tính chất:
- Tính xác định
- Tính hữu hạn hay tính dừng
- Tính đúng
Tuy nhiên có những trường hợp việc tìm ra một thuật toán với những tính chất đòi hỏi như trên rất khó khăn nhưng có cách giải có thể vi phạm các tính chất của thuật toán nhưng lại khá đơn giản và được chấp nhận Ví dụ những trường hợp bài toán có thể được phân tích và đưa tới việc giải một bài toán cùng loại nhưng cấp độ thấp hơn, chẳng hạn có dữ liệu nhập nhỏ hơn, giá trị cần tính toán nhỏ hơn, v.v Ta cũng thường thấy những định nghĩa về những đối tượng, những khái niệm dựa trên chính những đối tượng, những khái niệm đó như những ví dụ dưới đây
Ví dụ 1: Ðịnh nghĩa giai thừa
Giai thừa của một số tự nhiên n, ký hiệu là n!, được định nghĩa bằng cách qui nạp như sau:
0! = 1,
n! = (n-1)!*n, với mọi n > 0
Ví dụ 2: Ðịnh nghĩa dãy số Fibonacci f1, f2, , fn, :
f0 = 1,
Trang 4f1 = 1,
fn = fn-1 + fn-2 , vớ mọi n > 1
Thuật toán để giải các bài toán trong những trường hợp như trên thường được viết dựa trên chính nó, tức là trong các bước của thuật toán có thể có trường hợp thực hiện lại chính thuật toán đó (nhưng thường là với dữ liệu nhập có cở thấp hơn, hay có cấp độ thấp hơn) Những thuật toán loại này được gọi là những thuật toán đệ quy
I.2 Định nghĩa theo đệ quy
Một khái niệm X được định nghĩa theo đệ quy nếu trong định nghĩa X có sử dụng ngay chính khái niệm X
Ví dụ 1: Định nghĩa số tự nhiên
- 0 là một số tự nhiên
- n là số tự nhiên nếu n - 1 là số tự nhiên
Ví dụ 2: Định nghĩa hàm giai thừa n!
- 0! = 1
- Nếu n > 0 thì n! = n(n - 1)!
I.3 Phân loại kĩ thuật đệ quy: có 3 loại
- Vét cạn: đi tới tất cả điểm dừng rồi mới quay lại
- Cắt tia Alpha – Beta và nhánh cận: là 2 kĩ thuật cho phép không cần thiết phải đi tới tất cả các điểm dừng mà chỉ cần đi đến 1 số điểm nào đó và dựa vào 1 số suy luận để có thể quay lui sớm
I.4 Cấu trúc của thuật toán đệ quy
Trong thuật giải đệ qui thường gồm 2 phần: phần cơ sở và phần đệ quy
Phần cơ sở gồm các trường hợp không cần thực hiện lại thuật toán, tức là các trường
hợp dừng mà ta có thể trực tiếp giải quyết được bài toán (hay không có yêu cầu gọi đệ qui) Trong thuật toán tìm số hạng thứ n của dãy Fibonacci ở trên, bước 1 trong thuật toán là phần cơ sở của thuật giải đệ qui
Trang 5Phần đệ quy là phần trong thuật toán có yêu cầu gọi đệ quy, tức là yêu cầu thực hiện
lại thuật toán ở cấp độ thấp hơn Trong thuật toán tìm số hạng thứ n của dãy Fibonacci
ở trên, bước 2 trong thuật toán là phần đệ quy Trong phần đệ quy, yêu cầu gọi đệ qui thường được đặt trong một điều kiện kiểm tra việc gọi đệ quy
Cần lưu ý rằng phần cơ sở luôn luôn phải có hay nói cách khác là phần đệ quy luôn luôn phải nằm trong điều kiện kiểm soát dừng đệ quy, vì nếu không thì thuật toán
sẽ bị lặp vô hạn do việc gọi đệ quy luôn được thực hiện
Về mặt cài đặt, nếu như có sử dụng biến cục bộ trong thủ tục hay hàm đệ quy thì các biến này được tạo ra và được đặt trong vùng nhớ "STACK" Do đó quá trình gọi đệ quy dễ gây ra tình trạng tràn stack (stack overflow) Trong nhiều trường hợp có thể được người ta tìm cách viết lại thuật toán đệ quy dưới dạng lặp
I.5 Kết luận :
Đệ quy là 1 công cụ có vai trò rất quan trọng đối với những người lập trình hiện nay Đệ quy tỏ ra rất mạnh mẽ trong các bài toán vét cạn, tìm kiếm, đồ thị… Hiện nay
có rất nhiều bài toán chưa có lời giải không đệ quy ( Ví dụ bài toán Tháp hà Nội) Vì vậy ta cần phải nghiên cứu và phát huy, áp dụng thuật toán này nhiều hơn nữa
Tuy vậy thuật toán đệ quy lại có độ phức tạp tương đối lớn, ta chỉ nên sử dụng chúng khi thực sự cần thiết
Phần II Phân tích thuật toán và chương trình :
II.1 Cấu trúc dữ liệu :
Để giải quyết bài toán đặt hậu trên đầu tiên ta phải tìm ra cách lưu cấu trúc dữ liệu cho
vị trí mỗi con hậu đặt vào trong bàn cờ kích thước n x n
Ta sẽ dùng 2 mảng ( 1 mảng 1 chiều, 1 mảng 2 chiều có 2 cột) để lưu trữ vị trí các quân
cờ và cũng phục vụ cho thuật toán đặt hậu thõa yêu cầu, đồng thời tiết kiệm không gian
bộ nhớ thay vì việc khai báo 1 ma trận 2 chiều kích thước n x n
Mảng thứ nhất là doc[n] [2] có n hàng và 2 cột mục đích của việc chọn mảng
này có 2 cột cũng là do mỗi hàng ta sẽ đặt 2 quân hậu, vì vậy mỗi phần tử của mỗi hàng
sẽ lưu lại vị trí của mỗi quân cờ trong 2 quân cờ được đặt vào hàng đó
Mảng thứ hai là mảng ngang[n] có n phần tử trong mảng này ta cho chúng chỉ nhận các phần tử 0, 1 hoặc 2 mảng này dùng để chứa số lượng hậu còn có thể đặt trên
cột này ( ngang[i]=2 nghĩa là cột i còn có thể đặt 2 quân hậu nữa) Khi ta tiến hành đặt
1 quân hậu vào cột i đồng thời ta phải cho ngang[i] – 1, giảm số lượng quân hậu có thể
đặt nữa ở cột i ngang[i] = 0 nghĩa là ở cột i không thể đặt thêm hậu nữa
Trang 6II.2 Thuật toán giải quyết:
- Ta tiến hành dùng thuật toán đệ quy để đặt các quân hậu thõa mãn yêu cầu
- Đầu tiên là khởi tạo dữ liệu ban đầu cho 2 mảng lưu dữ liệu Đối với mảng ngang[] khởi tạo giá trị ta cho các phần tử nhận giá trị 2, nghĩa là chưa có quân hậu nào được đặt trong mỗi cột Đối với mảng cot[] ta cho chúng nhận các giá trị -1 để phân biệt với các giá trị số nguyên dương mà chúng nhận được khi đã đặt hậu
- Kiểm tra điều kiện “doc[i][j]==-1&&ngang[k]>0&& doc[i][0]<k” xem tại vị trí (i,j) có thể đặt hậu hay không Điều kiện doc[i][0]<k làm cho quân thứ 2 đặt trong hàng
này luôn đặt sau quân đầu tiên ví dụ như ta đã đặt hậu (2,3) thì không thể lại đặt (3,2) được vì chúng giống như nhau, ràng buộc điều kiện quân thứ 2 phải luôn đặt sau quân đầu tiên
- Quân hậu được đặt tại ô (i,k) tương đương với việc cho doc[i][j] = k với j = 0 hoặc 1.
- Tiếp theo là gọi đệ quy đặt quân hậu tiếp theo:
if(j==0) dathau(i,j+1); else dathau(i+1,0);
- Đến khi thõa điều kiện “(i==n-1&&j==1)”thì gọi hàm in kết quả ra màn hình “(i
== n-1&&j = 1)” nghĩa là đã đặt được quân hậu cuối cùng trên bàn cờ
II.3 Chương trình:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
int doc[50][2],ngang[50],n;
unsigned long dem;
char tt,t;
void gach(int i, int j, int m)
{
int k;
for(k=1;k<=m;k++) {gotoxy(i,j); cprintf("||");j++;}
}
void gioithieu()
{
textcolor(LIGHTBLUE);// chu mau xanh duong nhat
gotoxy(35,2);
cprintf("WELCOME !");
Trang 7cprintf("***** -*****");
gotoxy(32,4);
cprintf(" -*** -");
textcolor(YELLOW);// chu mau vang
gotoxy(24,7);
cprintf("Khoa CNTT - DH Bach Khoa DN");
gotoxy(35,13);
cprintf("DE TAI ");
gotoxy(34,14);
cprintf(" -** -");
gotoxy(16,8);
cprintf("DO AN MON HOC CAU TRUC DU LIEU & THUAT TOAN");
textcolor(MAGENTA);// chu mau tin
gotoxy(13,5);
cprintf("==================================================");
gotoxy(13,10);
cprintf("==================================================");
gach(13,5,6); gach(61,5,6);
textcolor(LIGHTGREEN); // chua mau xanh la cay nhat
gotoxy(15,12);
cprintf("===============================================");
gotoxy(15,17);
cprintf("===============================================");
gach(15,12,6);gach(60,12,6);
gotoxy(3,19);
cprintf("Giao vien huong dan:");
gotoxy(3,21);
cprintf("Sinh vien thuc hien:");
gotoxy(3,25);
cprintf("Lop Nhom - ");
textcolor(WHITE);// chu mau trang
gotoxy(29,19);
cprintf("PHAN THANH TAO");
gotoxy(29,21);
cprintf("PHAN VAN TUAN");
gotoxy(29,22);
cprintf("PHUNG DUY THIEN");
gotoxy(29,23);
cprintf("NGUYEN TRUNG DUNG");
gotoxy(29,24);
Trang 8cprintf("TRUONG CONG HUNG");
gotoxy(7,25);
cprintf("07T1");
gotoxy(17,25);
cprintf("C4");
gotoxy(22,25);
cprintf("12A");
textcolor(RED);// chu mau do
gotoxy(17,15);
cprintf("Tren ban co nxn hay xep 2n con hau sao cho ");
gotoxy(17,16);
cprintf("tren moi hang va moi cot co dung 2 con hau");
gotoxy(24,27);
cprintf("Press any key to continue ");
getch();
clrscr;
}
void khoitao()
/* Khoi tao ban dau ngang[i]=2 nghia la cot i chua dat duoc con hau nao,
doc[i][j]=-1 nghia la dong i, cot j chua dat hau*/
{
int i,j;
for(i=0;i<n;i++) {
ngang[i]=2;
for(j=0;j<2;j++) doc[i][j]=-1;
} }
void printbis()
/* in ra cac duong ke de de phan biet*/
{
int k;
printf("\n|");
for(k=0;k<n;k++) printf(" -|");
}
/* Xuat ra man hinh theo dang toan do*/
void print1()
{
int i;
dem++;
Trang 9printf("\n%2d) ",dem);
for(i=0;i<n;i++)
printf(" [%2d,%2d]", doc[i][0]+1,doc[i][1]+1);
}
void print2()
{
int i,j;
char c;
printf("\nMuon thoat thi an phim t / T");
printf("\nMuon xem tiep bang cach bieu dien toa do thi an phim c / C");
dem++; // biến dem dùng để dem số cách đặt hậu thõa mãn
printf("\n\n%d)\n",dem);
printbis();
for(i=0;i<n;i++) {
printf("\n|");
for(j=0;j<n;j++)
if(j==doc[i][0||j==doc[i][1]]) printf(" X |"); else printf(" |");
printbis();
/* Tai o co dat hau thi in ra dau X*/
} printf("\n");
c=getch();
if(c=='T'||c=='t') exit(0);
if(c=='C'||c=='c') t='2';
}
void print3()
{
dem++;
}
void dathau(int i,int j)
{
int k;
for(k=0;k<n;k++)
if(doc[i][j]==-1&&ngang[k]>0&&doc[i][0]<k)
{
doc[i][j]=k;ngang[k] ;
if(j==0) dathau(i,j+1); else dathau(i+1,0);
if(i==n-1&&j==1) { if(t=='2') print1(); else if(t=='1') print2(); else print3();}
doc[i][j]=-1; ngang[k]++;
}
}
Trang 10void main()
{
clrscr();
gioithieu();
while(1) {
dem=0;
l1:
clrscr();
textcolor(RED);
gotoxy(14,3);cprintf("==========================================="); gotoxy(14,10);cprintf("==========================================="); gach(12,3,8);gach(55,3,8);
textcolor(WHITE);
gotoxy(15,5);cprintf("Hay chon 1 trong 2 cach trinh bay sau:");
gotoxy(15,6);cprintf("1: Trinh bay truc quan bang hinh ve");
gotoxy(15,7);cprintf("2: Trinh bay bang cach bieu dien toa do");
gotoxy(15,8);cprintf("3: Xuat ra tong so cach tim duoc");gotoxy(15,12);cprintf("Ban se chon cach trinh bay: ");
scanf("%s",&t);
if(t!='1'&&t!='2'&&t!=’3’) {gotoxy(15,14);printf("Ban da chon sai, hay chon lai"); getch(); goto l1;}
do {
clrscr();
printf("\nNhap vao so dong, cot cua ban co vua (khac 1) : ");
scanf("%d",&n);
if(n==1) {printf("\nNhap sai, hay nhap lai"); getch();}
} while(n<=1);
khoitao();
if(t=='1') printf("\nDau x la o co dat hau\n");
if(t=='3') printf("\nPlease wait ");dathau(0,0);
printf("\n\nDa in ra tat ca %d cach dat hau thoa man yeu cau de bai",dem);
printf("\nBan co muon tiep tuc khong ? (C/c)");
tt=getch();
if(tt!='c'&tt!='C') break;
}
}
II.4 Giải thích chương trình:
Như đã nói, mảng ngang[] để lưu số hậu còn có thể đặt tại mỗi cột, mảng doc[] để lưu tọa độ các quân hậu đặt trên bàn cờ Ý nghĩa của các hàm trong chương trình như sau:
Trang 11- Hàm gioithieu() in ra màn hình phông giới thiệu của nhóm làm đồ án không có tác dụng gì đến thuật toán chương trình
- Các hàm gach(int i, int j, int m), printbis() chỉ có vai trò trang trí cho dao diện không có vai trog trong thuật toán
- Hàm print1() xuất kết quả ra màn hình theo dạng tọa độ
- Hàm print2() xuất kết quả theo dạng hình vẽ ( luu ý vị trí đặt quân hậu có dấu X
- Hàm dathau(int i, int j) có vai trò quan trọng nhất trong chương trình
void dathau(int i,int j)
{
int k;
for(k=0;k<n;k++)
if(doc[i][j]==-1&&ngang[k]>0&&doc[i][0]<k)
{
doc[i][j]=k;ngang[k] ;
if(j==0) dathau(i,j+1); else dathau(i+1,0);
if(i==n-1&&j==1) { if(t=='2') print1(); else if(t=='1') print2();}
doc[i][j]=-1; ngang[k]++;
}
}
- Biến k có vai trò duyệt qua từng cột trên bàn cờ
- Điều kiện:
if(doc[i][j]==-1&&ngang[k]>0&&doc[i][0]<k)
Để kiểm tra tại vị trí (i, k) đã được đặt hậu chưa doc[i][0]<0 như đã nói để phân biệt quân thứ 2 đặt trong hàng i với quân thứ 1 đặt trong hàng này, quân thứ 2 phải đặt sau quân đầu tiên, không đặt trùng với quân đầu tiên Như vậy quân đầu tiên trong hàng luôn đặt được do khởi tạo doc[i][0]=-1 < k Đến quân thứ 2, điều kiện
doc[i][0]<k ràng buộc cho quân thứ 2 luôn đặt sau quân thứ nhất (theo thứ tự trên
bàn cờ) k là vị trí sắp đặt quân thứ 2 sau khi thõa mãn 2 điều kiện
doc[i][j]==-1&&ngang[k]>
- Việc đặt 1 quân hậu tại ô (i, k) tương ứng với câu lệnh:
doc[i][j]=k;ngang[k] ;
- Tiếp đến là gọi đệ quy thực hiện đặt quân hậu tiếp theo
if(j==0) dathau(i,j+1); else dathau(i+1,0);
Nếu đây là quân đầu tiên trên dòng thì gọi đặt quân tiếp theo, nếu không phải thì gọi đặt hậu cho dòng tiếp theo
- Sau khi đặt xong cả bàn cờ thì xuất kết quả ra màn hình
if(i==n-1&&j==1) { if(t=='2') print1(); else if(t=='1') print2();}
Nếu trước đó đã chọn phím ‘2’ thì gọi hàm print2(), nếu đã chọn phím ‘1’ thì gọi hàm print1()
- Sau khi đã xuất kết quả thì trả lại trạng thái cũ cho ô (i,k) để tìm cách đặt hậu khác
Trang 12doc[i][j]=-1; ngang[k]++
II.5 Kết quả chương trình:
Giao diện giới thiệu:
Giao diện lựa chọn:
Phím lựa chọn là “1“ hoặc “2“ nếu ấn nút khác sẽ báo sai và yêu càu nhập lại
Trang 13Nếu ban đầu ta chọn “1“ chương trình sẽ liệt kê kết quả theo dạng hình vẽ bàn cờ
Vì các cách xếp quân hậu thõa mãn đối với các trường hợp từ 5 hàng trở lên rất nhiều nên có thêm phím lựa chọn:
- Ấn phím t/T thoát
- Ấn phím c/C để chuyển sang cách trình bày tọa độ
Trang 14Nếu chọn “2“ chương trình sẽ liệt kê theo dạng tọa độ
Cách liệt kê [1, 2] [1, 3] [2, 3] nghĩa là :
Hàng thứ 1: đặt quân hậu ở cột thứ 1 và 2
Hàng thứ 2: đặt quân hậu ở cột thứ 1 và 3
Hàng thứ 3: đặt quân hậu ở cột thứ 2 và 3
Muốn quay lại thực hiện tiếp thì ấn c/C
Nếu chọn 3 thì chương trình sẽ liệt kê ra rổng số kết quả tìm được với n=8 trở lên thời gian tính toán hơi lâu nên ta phải chờ
Muốn quay lại thực hiện tiếp thì ấn c/C