Chương trình Pascal cho bài toán ABBANguyễn Xuân Huy Trong bài "ABBA hay phép đối xứng gương và các thao tác" đăng trong Tin học & Nhà Trường số 1/1999 có nói đến hai thuật toán giải bài
Trang 1Chương trình Pascal cho bài toán ABBA
Nguyễn Xuân Huy
Trong bài "ABBA hay phép đối xứng gương và các thao tác" đăng trong Tin học & Nhà Trường số 1/1999 có nói đến hai thuật toán giải bài chuyển các khối bê tông lát đường băng Thể theo yêu cầu của bạn đọc, trong số này chúng tôi sẽ giới thiệu chương trình thực thi và đánh giá độ phức tạp của hai thuật toán đó Chúng tôi cũng sẽ giới thiệu một thủ thuật đơn giản để đo thời gian cho các bạn tiện dùng sau này
Trước hết ta nhắc lại bài toán ở dạng vắn tắt và dễ lập trình như sau:
Bài toán (chuyển phần tử): Cho mảng nguyên a[1 N] và một số tự nhiên K<= N/2 Hãy
chuyển K phần tử từ đầu mảng về cuối mảng
Bài toán này được giải bằng hai giải thuật:
- Giải thuật tự nhiên với độ phức tạp K(N+1) phép gán
- Giải thuật đối xứng với độ phức tạp 3N phép gán
Trước hết ta tổ chức dữ liệu cho bài toán
Tổ chức dữ liệu
Ta dùng một mảng a chứa 16000 phần tử kiểu word Như vậy N sẽ là hằng số có giá trị
16000 Chọn K bằng một nửa của N, tức là chọn 8000 cho giá trị K Các đại lượng này sẽ được khai báo tổng thể như sau:
Const
N=16000;
K=8000;
Type Mang=array[1 N] of word;
Var a: Mang;
Ta sẽ triển khai chương trình cho hai giải thuật như đã giới thiệu trong bài viết ở số 1
Giải thuật tự nhiên
Sơ đồ 1 Sơ đồ thô
Trang 2Chuyển K lần, mỗi lần 1 phần tử từ đầu mảng về cuối mảng.
Sơ đồ 2 Sơ đồ tinh chế lần thứ nhất
for i :=1 to K do begin
x:= a[1];
{Dịch các phần tử a[2 N] về bên trái một vị trí.}
a[N]:=x;
end;
Để dịch các phần tử a[2 N] về bên trái 1 vị trí ta dùng sơ đồ 3 sau đây:
Sơ đồ 3 Dịch các phần tử a[2 N] về bên trái 1 vị trí
for j:=2 to N do
a[j-1]:=a[j];
Phối hợp các sơ đồ 2 và 3 ta thu được thủ tục ChuyenTuNhien sau đây:
Procedure ChuyenTuNhien;
Var i,j: word;
x: word;
begin
for i:=1 to K do
begin
x:=a[1]; {Lấy phần tử đầu }
{Dịch trái 1 đơn vị}
for j:=2 to N do
a[j-1]:=a[j];
a[N]:=x; {Đặt phần tử đầu vào cuối mảng}
end;
end;
Giải thuật đối xứng
Trước hết xin thay mặt Ban biên tập của phụ san Tin học và Nhà Trường thành thật xin lỗi bạn đọc về một vài lỗi chế bản trong bài đăng ở số 1 Chúng tôi xin đề nghị sửa lại hai lỗi sau:
Lỗi thứ nhất: Trang 12, cột 1, dòng 29 Tính chất 2 của phép đối xứng gương bị in sai,
thay cho dấu nháy kép các bạn sửa giúp lại thành dấu nháy đơn như sau: (AB)' = B'A'
Lỗi thứ hai: Trang 12, cột 1, dòng 49 Xin sửa lại như sau: (A'B')'=B''A''=BA
Trang 3Sau đây chúng ta triển khai giải thuật đối xứng, thực hiện nhanh hơn giải thuật tự nhiên
Sơ đồ 1 Chuyển đối xứng
Đặt A=a[1 K]; B=a[K+1 N], áp dụng công thức (A'B')'=B''A''=BA ta sẽ thu được ngay kết quả cần tìm Gọi DoiXung(d,c) là thủ thuật thực hiện phép đối xứng đoạn a[d c] của mảng a, nghĩa là Doixung(d,c)=(a[d c])' Ta có ngay giải thuật ChuyenDoiXung sau đây: Procedure ChuyenDoiXung;
Begin
DoiXung(1,K); {A'}
DoiXung(K+1,N); {B'}
DoiXung(1,N); {( )'}
End;
Sơ đồ 2 DoiXung
Thủ tục đối xứng đoạn a[d c] sẽ đổi chỗ từng cặp phần tử cách đều đầu và cuối như sau:
Procedure DoiXung(d,c: word);
Var x: word;
begin
while d
begin
x: =a[d];
a[d]:=a[c];
a[c]:=x;
inc(d);
dec(c);
end;
end;
Kiểm thử chương trình
Để kiểm thử chương trình, chúng ta thực hiện theo sơ đồ sau:
Sơ đồ 1 Kiểm thử một giải thuật
Với mỗi giải thuật ta kiểm thử theo quy trình 5 bước sau:
Bước 1 Sinh ngẫu nhiên dữ liệu cho mảng a.
Bước 2 Ghi nhận thời điểm trước khi thực hiện thủ tục chuyển.
Bước 3 Thực hiện thủ tục chuyển (gọi thuật toán ChuyenTuNhien hoặc thuật toán
ChuyenDoiXung)
Trang 4Bước 4 Ghi nhận thời điểm sau khi thực hiện thủ tục chuyển.
Bước 5 Thông báo thời gian thực hiện (tức là độ phức tạp tính toán)./p>
Hai cách đo thời gian
Có hai phương thức ghi nhận thời gian trong môi trường Pascal Phương thức thứ nhất là dùng thủ tục Gettime(gio,phut,giay,ptgiay) với các biến kiểu word: gio (giờ), phut (phút), giay (giây) và ptgiay (phần trăm giây) Sau khi lấy được thời điểm trước và thời điểm sau, chúng ta phải tính ra số giây đã chi phí Việc tính toán này được thực hiện trong hệ đếm 60, riêng với phần trăm giây lại phải thực hiện theo hệ đếm thập phân Nếu chỉ cần tính đến giây, ta dùng cách quy đổi ra giây theo mẫu sau:
Sơ đồ 2
Gettime(gio,phut,giay,ptgiay);
Tongsogiaytruoc:=(gio*60+phut)*60+giay;
Thực hiện thủ tục
Gettime(gio,phut,giay,ptgiay);
Tongsogiaysau:=(gio*60+phut)*60+giay;
Tongthoigian:=Tongsogiaysau - Tongsogiaytruoc;
Cách thực hiện trên đòi hỏi nhiều biến và tính toán phiền phức Xin giới thiệu với bạn đọc phương thức truy nhập thời gian một cách trực tiếp Hệ điều hành dành 4 byte trong RAM bắt đầu từ địa chỉ 46c (theo hệ 16) để lưu trữ nhịp làm việc của máy tính Ta có thể truy nhập trực tiếp đến giá trị này bằng cách khai báo một biến mà ta đặt tên là Nhip Biến này phải có độ lớn 4 bytes cho nên ta định kiểu cho nó là longint Biến này phải được cấp phát vào đúng địa chỉ 46c Muốn vậy, bạn phải viết khai báo sau đây:
Nhip: longint absolute $0000:$046c;
Khai báo trên có nghĩa: Cấp phát biến Nhip kiểu longint tại địa chỉ tuyệt đối 46c (hệ 16)
Sử dụng thêm 2 biến t1 và t2 kiểu longint, ta có thể tính được số Nhip cần thiết sau mỗi lần gọi thủ tục nào đó Sơ đồ là như sau:
Sơ đồ 3.
t1: = Nhip;
Thực hiện thủ tục;
t2:=Nhip;
TongsoNhip:=t2-t1;
Tongthoigian:=TongsoNhip/18.2;
Trang 5Dòng cuối cùng của sơ đồ trên cho ta số giây cần thiết Vì mỗ giây có 18.2 nhịp nên chia
số nhịp cho giá trị này ta thu được số giây Lưu ý bạn đọc rằng biến Tongthoigian phải được khai báo theo kiểu real Còn biến TongsoNhip có thể khai báo là longint
Chương trình
Trong chương trình dưới đây thủ tục Gen sinh N giá trị khởi đầu cho mảng a theo luật, a[i]=i, i=1 N Cấp theo cách đơn giản này sẽ giúp ta dễ theo dõi hoạt động của chương trình
Thủ tục Test1 sẽ thực hiện theo giải thuật tự nhiên, còn thủ tục Test2 dành cho giải thuật đối xứng Với dữ liệu đã cho, Test1 đòi hỏi khoảng 418 nhịp tương ứng với 23 giây, còn Test2 chỉ đòi hỏi 1 nhịp và do đó chỉ tốn 1/18 giây
Thủ tục Doi(s) hiển thị thông báo s trên màn hình và đợi người sử dụng ấn phím để chuyển qua thủ tục khác Thủ tục Xem hiển thị giá trị của mảng a trên màn hình
(*CHUYEN.PAS*)
Uses crt;
Const N=16000;
K=8000;
Type
Mang=array[1 N] of word;
Var Nhip: longint absolute $0000:$046c;
a: Mang;
t1,t2: longint;
Procedure Doi(s: string);
Begin
writeln;
write(s);
repeat until readkey <> #0;
writeln;
End;
Procedure Gen;
Var i: word;
begin
for i:=1 to N do
a[i]:=i;
end;
Procedure Xem;
Var i: word;
begin
Trang 6for i:=1 to N do
write(a[i]:8);
end;
Procedure ChuyenTuNhien;
Var i,j: word;
x: word;
begin
for i:=1 to K do
begin
x:=a[1]; {Lấy phần tử đầu }
{Tịnh tiến 1 vị trí}
for j:=2 to N do
a[j-1]:=a[j];
a[N]:=x; {Đặt phần tử đầu vào cuối mảng}
end;
end;
Procedure Test1;
Begin
clrscr;
Gen;
Doi('Bam phim tuy y de xem du lieu vao cho giai thuat tu nhien:'); Xem;
Doi(' Bam phim tuy y de thuc hien giai thuat tu nhien:');
writeln('Voi may PC 486 ban phai doi chung 23 ');
t1:=Nhip;
ChuyenTuNhien;
t2:=Nhip;
Doi('Ket thuc giai thuat tu nhien Bam phim tuy y de xem du lieu ra:'); Xem;
Doi('Bam phim tuy y de xem thong bao:');
write('Thoi gian thuc hien giai thuat tu nhien:', t2-t1 , '(nhip)='); writeln(((t2-t1)/18.2):0:0,'(giay)');
Doi('Bam phim tuy y de ket thuc:');
end;
Procedure DoiXung(d,c: word);
Var x: word;
begin
while d
begin
x: =a[d];
a[d]:=a[c];
a[c]:=x;
inc(d);
Trang 7end;
Procedure ChuyenDoiXung;
Begin
DoiXung(1,K);
DoiXung(K+1,N);
DoiXung(1,N);
End;
Procedure Test2;
begin
clrscr;
Gen;
Doi('Bam phim tuy y de xem du lieu vao cho giai thuat doi xung:'); Xem;
Doi('Bam phim tuy y de thuc hien giai thuat doi xung:');
t1:=Nhip;
ChuyenDoiXung;
t2:=Nhip;
Doi('Ket thuc giai thuat doi xung Bam phim tuy y de xem du lieu ra:'); Xem;
Doi('Bam phim tuy y de xem thong bao:');
write('Thoi gian thuc hien giai thuat doi xung:',t2-t1 , '(nhip)=');
writeln(((t2-t1)/18.2):0:0,'(giay)');
Doi('Bam phim tuy y de ket thuc:');
end;
BEGIN
Test2;
Test1;
END