Professional Documents
Culture Documents
Bai Tap BFS
Bai Tap BFS
--------------------------------------------------
THUẬT TOÁN LOANG TRÊN MA TRẬN (DÃY 2 CHIỀU)
------Tư tưởng của thuật toán loang thật ra cũng rất đơn giản. Hãy tưởng tượng có một nàng công chúa bị bắt giam vào 1 mê cung, nàng bí quá, lôi ĐTDĐ ra fone cho hoàng tử.
Hoàng tử tức tốc dẫn theo một đạo quân (giả sử rằng số quân của chàng ta là ko giới hạn). Khi đến cổng vào mê cung, chàng may mắn gặp một "bậc thánh nhân", người cho chàng
biết rằng công chúa đang bị giam trong phòng [X,Y] nào đó. À, bây giờ trong đầu chàng nghĩ đến 2 hướng sau:
_ Chàng quyết định solo đi vào mê cung, dĩ nhiên trên tay cầm 1 cuộn chỉ. Khi vào cổng, chàng bắt gặp T căn phòng, chàng sẽ đi vào từng phòng một, bước vào T1, chàng lại gặp
T2 cửa, ... , cứ thế đến khi ko có đường đi, chàng sẽ lần ngược theo đường chỉ, đánh dấu vào cánh cửa chàng đã đi rồi, và chọn 1 cửa khác. Với cách này, chàng sẽ tìm được phòng
có công chúa, dĩ nhiên, nếu chàng ko gục ở giữa đường.
_ Tận dụng nguồn binh lực hùng hậu của mình, chàng ra hiệu lệnh "Toàn lực lục soát". Đi vào mê cung, bắt gặp T căn phòng, chàng chia đoàn binh thành T đạo, cứ thế. Dĩ nhiên,
mỗi binh sĩ đều đc trang bị phương tiện liên lạc để khi tìm thấy công chúa sẽ phát hiệu lệnh "Rút lui". Vậy, với cách này ta sẽ tìm được công chúa với quãng đường đi từ cửa mê
cung đến phòng giam là ngắn nhất. Nhưng, nảy sinh một bất cập là, nếu binh sĩ tìm được công chúa rồi, thì làm sao lần ra lại cửa mê cung đây??? Đương nhiên hoàng tử ko thể
chấp nhận chuyện để người yêu mình chung phòng với một đám binh sĩ ~~. Chàng ra lệnh rằng, trước khi vào mỗi cửa, phải dùng kiếm vạch mũi tên xuống đất, mũi tên này sẽ
hướng đến cửa các binh sĩ đã đi vào trước khi gặp căn phòng. Ví dụ khi vào cổng mê cung, 1 tốp binh sĩ đi vào cửa T1, gặp N2 cửa khác, chia binh lực ra, với mỗi cửa T2, các binh sĩ
sẽ vạch mũi tên hướng về cửa T1, tức cửa đã dẫn họ tới N2 căn phòng. Nhờ đó họ sẽ lần được đường thoát khỏi mê cung.
Từ đây, ta sẽ hình thành 2 phương pháp tìm đường đi, trong bài viết này, chúng ta sẽ tìm hiểu cách thứ hai, mà theo cách gọi thông thường là LOANG: Từ một vị trí có tọa độ
A(x,y) cho sẵn, bạn buộc phải tìm đến một vị trí B(x’,y’) sao cho quảng đường đi là ít nhất có thể và in ra đường đi từ A đến B đó.
------Có khá nhiều phương pháp tìm đường đi, trong đó có BFS (Breadth First Search - Tìm kiếm theo chiều rộng, còn gọi là loang), DFS (Depth First Search - Tìm kiếm theo
chiều sâu). Như đã nói, chúng ta chỉ xem xét vềBFS trong bài viết này. Có nhiều cách cài đặt BFS như là hàng đợi (queue) hoặc dùng 2 mảng tính qua lại lẫn nhau (mảng chứa
những phòng có thể đi tiếp, và mảng chứa những phòng bắt gặp khi đi vào những phòng ở trên). Tuy nhiên các mà tôi sắp giới thiệu cho bạn đây là một cách không chính thống,
phương pháp này loang theo chiều rộng và cách thức tốn khá nhiều tài nguyên. Song, với thuật toán Loang này, bạn sẽ dễ dàng làm những bài toán Loang đơn giản hay phức tạp
với dữ liệu yêu cầu nhỏ. Thuật toán được trình bày ngắn gọn như sau:
------Cứ một nơi bạn đặt chân tới và thấy là nơi cho phép thì đánh dấu nơi đó với 1 số [i]k tăng dần (để xác định rằng bạn đã đi đến đó và ô đó là nơi bạn đi đến lần thứ k). Cứ tiếp
tục như thế cho đến khi gặp điểm dừng thì thoát khỏi chương trình.
var a : array[1..100,1..100] of byte; khai báo mả ng kiể u dữ liệ u số nguyên đế m đượ c
Near : array[1..2] of record khai báo mả ng kiể u bả n ghi
x,y : byte;
end;
Procedure Loang;
Var i,j,z : byte;
Begin
Repeat {Kiểm tra nếu phần tử cuối cùng a[n,m] khác 0 (gặp điểm dừng) thì sẽ kết thúc, ngược lại thì làm những công việc sau }
Flag := false; {Hiện tại, chưa tìm đc chỗ để loang}
For i := 1 to n do
For j := 1 to m do {Duyệt tất cả phần tử mảng}
Begin
If (a[i,j] = e) then {Nếu phát hiện có dấu chân đc đánh dấu tại bước loang thứ e thì bắt đầu loang tiếp}
Begin
{Tạo những phần tử xung quanh i,j. Ở đây tôi làm dưới và phải}
Near[1].x := i+1; Near[1].y := j;
Near[2].x := i; Near[2].y := j+1;
Ma trận gốc:
00101
00011
00001
00000
Ma trận Loang:
12101
23411
34561
45678
Ma trận đã xóa Loang dư:
10101
20011
30001
45678
Xong. Thuật toán cài đặt khá đơn giản và chạy khá hiệu quả, tuy nhiên thời gian khá chậm do độ phức tạp tương O(k*n^2). Bạn có thể thêm một chút cải tiến để cải thiện tốc độ.
Bài 1 : Tìm dãy con tăng dài nhất :
Cho mảng A(N) . Cần xoá ít nhất một số phần tử của dãy này sao cho các phần tử còn lại tạo thành dãy tăng nghiêm ngặt . Ta xây dựng mảng T(N) và mảng D(N) với ý nghĩa :
+ T[i] là chỉ số j ( trong mảng A ) của phần tử đứng trước phần tử có chỉ số i (trong mảng A ) khi xét dãy kết quả .
+ D[i] là độ dài của dãy kết quả khi bài toán trên mảng A(N) mới được xét từ phần tử 1 đến phần tử i ( Nghiã là ta tạm giải bài toán với kích thước đến i - đó là kích thước không vượt quá kích thước bài toán đã cho ) .
Công thức qui hoạch động của bài toán này là :
D[i] = Max { D[j] + 1 / " j : j <i mà A[j] < A[i] }
Chú ý
1- Khởi trị bài toán : T[1] = 0 , D[1] = 1
2- Xử dụng công thức trên với i>1 , mỗi lần tìm được j thoả mãn cần ghi lưu T[i]=j
3- Khi xong i=N , tìm lại kết quả nhờ duyệt mảng D . Trước hết tìm j có D[j] lớn nhất , sau đó truy hồi dần tìm j bằng cách thay j bởi T[j] cho đến khi T[j]=0
Các vị trí j tìm được chính là các vị trí trong mảng A cần giữ lại nhưng theo thứ tự ngược .
Thí dụ
Chỉ số 1 2 3 4 5 6 7 8 9 10 11 12
A 6 12 8 11 3 4 1 7 5 9 10 2
T 0 1 1 3 0 5 0 6 6 8 10 7
D 1 2 2 3 -1 -2 1 -3 3 -4 -5 2
Dãy kết quả là :
Chỉ số 5 6 8 10 11
A 3 4 7 9 10
Bài 2 : Xoá ít nhất một số phần tử của 2 dãy sao cho sau khi xoá 2 dãy như nhau
Gợi ý : Cần xây dựng mảng C(N,M) với ý nghĩa : C[i,j] là độ dài của dãy còn lại nếu dãy A chỉ xét tới chỉ số i và dãy B chỉ xét tới chỉ số j . Đương nhiên C[1,j] = 0 nếu A[1] không có trong dãy B(j) ,ngược lại thì C[1,j] = 1 ;
tương tự C[i,1] = 0 nếu B[1] không có trong dãy A(i) ,ngược lại thì C[i,1] = 1 . Những trường hợp còn lại C[i,j] được tính theo công thức truy hồi sau :
C[i,j] = Max{C[i,j-1],C[i-1,j],C[i-1,j-1]+x}
( Vì : nếu A[i]=B[j] thì C[i,j]= C[i-1,j-1]+1
nếu A[i] không có trong B(j) thì C[i,j]= C[i-1,j]
nếu B[j] không có trong A(i) thì C[i,j]= C[i,j-1] )
A - Lời giải
Bài 1 :
{Cho N nguyen duong N<=10000 so , tim day tang dai nhat }
Uses Crt;
Const Max = 10000; {Khai báo biến không đổi}
Fi = 'BL14.inp'; {Tạo file nhập}
Fo = 'BL1.out'; {File xuất}
Type Mang = Array[1..Max] of Integer;
Var A,D,T : Mang;
N,dem : LongInt;
F : Text;
Procedure TaoF;
Var F : Text;
i : LongInt;
Begin
Assign(F,Fi);Mở tập tin có tên là Fi
ReWrite(F);
Write('Nhap N = ');
Repeat
{$I-} Readln(N);{$I+}
Until (IoResult=0) and (N>0) and (N<=Max);
Writeln(F,N);
For i:=1 to N do
Begin
Write(F,10000-i+1:7);
{Write(F,Random(100):3);}
If i mod 10 =0 then Writeln(F);
End;
Close(F);
End;
Procedure Nhap;
Var i : LongInt;
Begin
FillChar(A,Sizeof(A),0); (FillChar Ðiền một số byte có giá trị vào; Sizeof: cho kích thước của biến hay kiểu dữ liệu)
Assign(F,Fi);
Reset(F);
Readln(F,N);
For i:=1 to N do Read(F,A[i]);
Close(F); Inc(dem)
End; End;
Procedure KhoitriT_D; Until i=0;
Begin End;
FillChar(T,Sizeof(T),0); Procedure Ghi;
T[1] := 0; Var F : Text;
D[1] := 1; i : LongInt;
End; Begin
Function Vitritruoc(i : LongInt) : LongInt; Assign(F,Fo);
Var j,Ld,Lj : LongInt; ReWrite(F);
Begin Writeln(F,dem);
Ld := 0; dem := 0;
Lj := 0; For i := 1 to N do
Vitritruoc := 0; If D[i]<0 then
For j:=i-1 downto 1 do Begin
If A[j]<=A[i] then Write(F,A[i]:7);
If D[j]>Ld then Inc(dem);
Begin If dem mod 10 = 0 then Writeln(F);
Ld := D[j]; End;
Lj := j; Close(F);
End; End;
Vitritruoc := Lj; BEGIN
End; Clrscr;
Procedure TaoT_D; {TaoF;}
Var i : LongInt; Nhap;
Begin TaoT_D;
KhoitriT_Dl; Timketqua;
For i:=2 to N do Ghi;
Begin Writeln('Da xong ');
T[i] := Vitritruoc(i); Readln;
If T[i]=0 then D[i] := 1 END.
Else D[i] := D[T[i]]+1; Bài 2 :
End; Uses Crt;
End; Const Max = 100;
Function MaxD : Longint; Fi = 'qhdong2.inp';
Var i,p,Li : LongInt; Fo = 'qhdong2.out';
Begin Type M1 = Array[1..Max] of Byte;
p := -MaxInt; M2 = Array[1..Max,1..Max] of Byte;
Li := 0; Var A,B : M1;
For i:=1 to N do C : M2;
If D[i]>p then F,F2 : Text;
Begin M,N : Byte;
p := D[i]; Procedure TaoF;
Li := i; Var i : Byte;
End; F : Text;
MaxD := Li; Begin
End; Write('Nhap do dai 2 mang : M,N = ');
Procedure Timketqua; Readln(M,N);
Var i : LongInt; Assign(F,Fi);
Begin ReWrite(F);
i := MaxD; Writeln(F,M,' ',N);
D[i] := -D[i]; For i:=1 to M do Write(F,Random(100+1),' ');
dem := 1; Writeln(F);
Repeat For i:=1 to N do Write(F,Random(100+1),' ');
i := T[i]; Close(f);
If i<>0 then End;
Begin Procedure DocF;
D[i] := -D[i]; Var i : Byte;
Begin Function Max3(x,y,z : Byte) : Byte;
Assign(F,Fi); Var phu : Byte;
Reset(F); Begin
Readln(F,M,N); If x>y then phu := x Else phu := y;
For i:=1 to M do Read(F,A[i]); If z>phu then phu := z;
Readln(F); Max3 := Phu;
For i:=1 to N do Read(F,B[i]); End;
Close(F); Function Co(x,L : Integer;D : M1) : Boolean; Var : Byte;
End;
Bài 1
cho xâu S ( độ dài không vượt quá 10) chỉ gồm các kí tự 'A' đến 'Z', các ký tự đôi 1 khác nhau. hãy liệt kê các hoán vị khác nhau của xâu S
Dư liệu vào: nhập từ file HVX.INP gồm 1 dòng duy nhất là xâu S
Dữ liệ ra: Ghi ra file HVX.OUT gồm nhiều dòng, mỗi dòng là 1 hoán vị của xâu S
Bài 2
Cho số nguyên dương n (n<=20). hãy liệt kê tất cả các xâu độ dài n chỉ gồm 2 ký tự 'A' và 'B' sao cho ko có 2 ký tự 'B' nào đứng cạnh nhau.
Dữ liệu vào: nhập từ file AB.INP gồm 1 dòng duy nhất ghi số nguyên dương n
Dữ liệu ra: Ghi ra file AB.OUT gồm nhiều dòng, mỗi dòng là 1 xâu độ dài n tìm được
Bài 3
Cho dãy số A gồm n ( n<=10) số nguyên A1 ,A2, . . ,An và 1 số nguyên dương k ( 1<k<n) Hãy đưa ra 1 cách chia dãy số thành k nhóm sao cho các nhóm có tổng bằng nhau.
Dữ liệu vào: Nhập từ file CHIA.INP gồm dòng đầu tiên chứa số nguyên n và k; dòng sau chứa dãy số nguyên A
Dữ liệu ra: Ghi ra file CHIA.OUT gồm k dòng, mỗi dòng là 1 nhóm.
Bài 4 ( làm cũng đc. hì )
1 xâu X = x1,x2,...,xm đc gọi là xâu con của xâu Y = y1,y2,...,yn. Nếu ta có thể nhận đc xâu X từ xâu Y bằng cách xóa đi 1 số kí tự, tức là tồn tại 1 dãy các chỉ số:
1<=i1<i2<...<im<= n để x1=yi, x2 = yi,..., xm = yi. Ví dụ X = 'adz' là xâu con của Y = 'baczdtz',
i1 = 2 < i2 = 5 < i3 = 7.
Bai 1
Program hoanvi;
Uses crt;
Const
maxLS = 255;
Var
s,t:string;
ls:integer;
bo: array [1..maxLS] of boolean;
i:integer;
F:text;
{-----------------------------------------------------------------------}
Procedure hvi(n:integer);
Var
i:integer;
c:char;
Begin
If (n = 0) then
Begin
Writeln(F,t);
Exit;
End;
For i:=1 to ls do
If bo[i] then
Begin
bo[i]:=False;
t[n]:=s[i];
hvi(n-1);
bo[i]:=True;
End;
End;
{-----------------------------------------------------------------------}
BEGIN
Clrscr;
Assign(F,'HVX.INP');
Reset(F);
Read(F,s);
Close(F);
Assign(F,'HVX.OUT');
Rewrite(F);
t:=s;
ls:=length(s);
For i:=1 to ls do
bo[i]:=true;
hvi(ls);
Close(F);
END.
Bai 2
fi = 'CHIA.INP';
Var B:array[1..100] of char;
fo = 'CHIA.OUT';
n:integer;Procedure printresult;
var
Var j:integer;check:boolean; Begin check:=true; For j:=1 to
n-1 do n,k:integer;
readln; begin
.
Bai 4
var i,j:integer; find:boolean;
program xau_con;
begin
var
for i:=1 to length(x) do
x,y:string;
begin
{_}
find:=false;
procedure enter;
for j:=1 to length(y) do
begin
if x[i]=x[j] then begin delete(y,1,j); find:=true; break; end;
readln(x);
if (not find) then exit(false);
readln(y);
end;
end;
exit(true);
{_}
end;
function solve:boolean;
{_}
procedure output; Begin
writeln(solve); write(f,B[j]);
end; writeln(f);
{_} end;
begin End;
enter; {---------------------------------------------}
Hướng dẫn:
Dùng thuật toán tìm kiếm xác định các miền liên thông. Trong mỗi miền liên thông đếm số cừu và số sói trong đó. Nếu số cừu lớn hơn số sói thì coi như số sói còn lại trong miền này bằng 0, ngược lại số cừu còn lại
trong miền này bằng 0. Khi tìm kiếm tới ô nào thì xóa ô đó bằng cách gán kí tự ‘#’ trên ô đó.Chương trình:
program dem_soi_cuu;
const MAXN = 500; begin
MAXM = MAXN; x:=qx[qk];
MAXQ = MAXN*MAXM; y:=qy[qk];
fi='DEM.INP'; fo='DEM.OUT';var A : array[0..MAXN+1, 0..MAXM+1] of char; if qk<MAXQ then qk:=qk+1 else qk:=0;
n, m, i, j, x, y, socuu, sosoi, tsosoi, tsocuu : longint; qx : array[0..MAXQ] of end;procedure Duyet(x, y : longint);
longint; begin
qy : array[0..MAXQ] of longint; if A[x, y]<>'#' then
qp, qk : longint; begin
f,g:text; push(x, y);
procedure input; if A[x, y]='v' then sosoi:=sosoi+1
var i,j:integer; else if A[x, y]='o' then socuu:=socuu+1;
begin A[x, y]:='#';
assign(f,fi); reset(f); end;
readln(f,n,m); end;begin input;
for i:=1 to n do qp:=0;
begin qk:=0;
for j:=1 to m do read(f,a[i,j]); tsosoi:=0;
readln(f); tsocuu:=0; for i:=1 to n do
end; for j:=1 to m do if (A[i, j]='v')or(A[i, j]='o') then
close(f); begin
end;procedure push(x, y : longint); socuu:=0;
begin sosoi:=0;
qx[qp]:=x; Duyet(i, j);
qy[qp]:=y; while qp<>qk do
if qp<MAXQ then qp:=qp+1 else qp:=0; begin
end;procedure pop(var x, y : longint); pop(x, y);
Duyet(x+1, y); tsosoi:=tsosoi+sosoi;
Duyet(x, y+1); end;
Duyet(x-1, y); assign(g,fo); rewrite(g);
Duyet(x, y-1); writeln(g,tsocuu, ' ', tsosoi);
end; close(g);
if socuu>sosoi then sosoi:=0 else socuu:=0; end.
tsocuu:=tsocuu+socuu;
Bài 4. Chuyển bi
Cậu bé vẽ N (N<=100) vòng tròn, đánh số từ 1 tới N và tô màu các vòng tròn đó (có thể có các vòng tròn có màu giống nhau). Sau đó nối từng cặp lại bằng các cung định hướng, mỗi cung có một màu nhất định. Các
màu (của cung và của vòng tròn) được đánh số từ 1 đến 100.
Cậu bé chọn 3 số nguyên khác nhau L, K và Q nằm trong phạm vi từ 1 tới N, đặt vào trong các vòng tròn số L và K mỗi vòng một hòn bi, sau đó bắt đầu di chuyển bi theo qui tắc sau:
- Bi chỉ được chuyển theo cung có màu trùng với màu của vòng tròn chứa viên bi thứ 2.
- Bi chỉ được chuyển theo chiều cung.
- Hai viên bi không được đồng thời ở cùng một vòng tròn.
- Quá trình di chuyển kết thúc, khi một trong hai viên bi tới vòng tròn Q.
Hãy lập trình xác định cách di chuyển để chấm dứt quá trình sau một số ít nhất các bước chuyển.
Dữ liệu: vào từ file BI.INP:
- Dòng đầu: 4 số nguyên N, L, K, Q
- Dòng thứ 2: N số nguyên C1, C2, …, CN (Ci: là màu của vòng tròn i).
- Dòng thứ 3: số nguyên M (0<=M<=10000).
- M dòng sau: mỗi dòng 3 số nguyên Ai Bi Di xác định cung màu Di đi từ vòng tròn Ai tới vòng tròn Bi. Các số trên một dòng cách nhau 1 dấu cách.
Kết quả: đưa ra file BI.OUT:
- Dòng đầu: YES hoặc NO cho biết quá trình có kết thúc được hay không.
- Nếu dòng đầu là YES thì dòng 2 chứa số nguyên xác định số bước chuyển tối thiểu.
Hướng dẫn:
Trong bài này ta xét một kỹ thuật tìm kiếm theo chiều rộng (loang) phát triển thêm đó là loang đổ mảng: trong quá trình loang, ta thường hay dùng một hàng đợi để lưu lại toàn bộ các trạng thái đã thăm và chuẩn bị thăm.
Nhưng ta chú ý loang là tìm kiếm theo mức, từ một mức khởi đầu sẽ sinh ra mức tiếp theo và từ mức tiếp theo sẽ sinh ra mức tiếp theo nữa… Khi đó, mức đã được dùng để sinh không còn cần thiết nữa nghĩa là ta hoàn toàn có
thể loại bỏ chúng ra khỏi hàng đợi để lấy "chỗ" cho các trạng thái sau. Từ ý tưởng trên, ta sẽ dùng 2 mảng khác thay thế cho 1 hàng đợi để lưu lại 2 mức liên tiếp nhau.
Ở bài tập này, một trạng thái được ghi nhận là số thứ tự của 2 vòng tròn chứa 2 viên bi và ta coi nó là một nút của cây tìm kiếm, ký hiệu (i,j).
Gọi nhãn của một nút là f(i,j): f(i,j)=0 hoặc f(i,j)=1 cho biết nút đó chưa thăm hay đã thăm. Như vậy một nút sẽ được thăm nếu nó thỏa mãn mọi điều kiện của bài toán và nhãn của nó có giá trị 0. Từ đó, ta suy ra rằng
một mức sẽ có không quá n2 nút. Do vậy, ta sẽ dùng 2 mảng q1, q2 kích thước n*n để lưu lại các nút của 2 mức liên tiếp.
Chương trình:
const inp=’bi.inp’; Procedure inkq;
out=’bi.out’; Begin writeln(fi,’YES’);
var a,q1,q2,f:array[1..100,1..100] of byte; writeln(fi,dem);
c:array[1..100] of byte; close(fi);
n,v1,v2,v3:byte; halt;
m,dem:integer; end;
stop:boolean;
fi:text; Procedure play;
var i,j,k:byte;
Procedure docf; begin
var i,j,mau:byte; for i:=1 to n do
k:integer; for j:=1 to n do
begin if q1[i,j]=1 then
assign(fi,inp); reset(fi); readln(fi,n,v1,v2,v3); for k:=1 to n do
for k:=1 to n do read(fi,c[k]); readln(fi); if (k<>j) and (a[i,k]=c[j]) and (f[k,j]=0)
readln(fi,m); fillchar(a,sizeof(a),0); then
for k:=1 to m do begin
begin readln(fi,i,j,mau); q2[k,j]:=1;q2[j,k]:=1;
a[i,j]:=mau; f[k,j]:=1;f[j,k]:=1;
end; stop:=false;
close(fi); if k=v3 then inkq;
end; end;
end;
Procedure initBFS; Procedure BFS;
begin begin dem:=0; {bắt đầu ở mức 0}
fillchar(f,sizeof(f),0) {khởi tạo tất cả các nút đều chưa repeat inc(dem); stop:=true;
thăm} play;
fillchar(q1,sizeof(q1),0){mức khởi đầu chưa có nút nào} if stop=false then
fillchar(q2,sizeof(q2),0) {mức tiếp theo cũng chưa có nút begin
nào} q1:=q2; {ghi đè mức mới lên mức
q1[v1,v2]:=1;q1[v2,v1]:=1; cũ}
f[v1,v2]:=1;f[v2,v1]:=1; fillchar(q2,sizeof(q2),0); {khởi
end; tạo mức mới chưa có nút nào}
end; end;
until stop;
Begin
Procedure xuly; docf;
begin xuly;
assign(fi,out); rewrite(fi); initBFS; BFS; End.
writeln(fi,’NO’); close(fi);
Với nội dung file ‘b3.inp’ :hàng i chứa khối lượng a[i], giá trị c[i]:
34
45
7 10
8 11
9 13
Qua hai ví dụ trên chắc các bạn đã nắm được tư tưởng của thuật toán qui hoạch động cũng như cách cài đặt cho nó. Như các bạn thấy, cách phát biểu thuật toán rất đơn giản. Nếu biết cách vận dụng thuật toán một cách hợp lý, ta
có thể giải được một lớp khá rộng các bài toán trong thực tế. Hi vọng thuật toán sẽ là công cụ tốt của các bạn trong quá trình học tập môn tin học.
Tập tin định kiểu
Tập tin định kiểu là tập tin mà các phần tử (mẫu tin) của nó có cùng độ dài.
Kiểu tập tin có thể là kiểu cơ sở như ký tự, nguyên, thực,... hoặc kiểu có cấu trúc như mảng, bản ghi,...
a. Cách khai báo + Khai báo gián tiếp
TYPE
<Kiểu File> = FILE OF <Kiểu phần tử> ;
VAR
<Biến File> : <Kiểu File> ;
Ví dụ
TYPE {Ðịnh nghĩa các kiểu tập tin}
FileNguyen = FILE OF integer ;
FileReal = FILE OF real ;
FileKyTu = FILE OF char ;
NhanSu = RECORD
HoTen : string[25] ;
Tuoi : Byte ;
Dchi : string[35] ;
Luong : real ;
END ;
FileNhanSu = FILE OF NhanSu ;
VAR
FN : FileNguyen ;
FR : FileReal ;
FC : FileKyTu ;
FP : FileNhanSu ;
+ Khai báo trực tiếp
VAR
<Biến File> : FILE OF <Kiểu phần tử> ;
Ví dụ
TYPE
NhanSu = RECORD
HoTen : string[25] ;
Tuoi : Byte ;
Dchi : string[35] ;
Luong : real ;
END ;
VAR
FN : FILE OF integer ;
FR : FILE OF real ;
FC : FILE OF char;
FP : FILE OF NhanSu ;
Trong các ví dụ trên, ta có:
- Biến tập tin FN là một tập tin kiểu nguyên với mỗi phần tử của chúng là một số nguyên và có độ dài là 2 bytes.
- Biến tập tin FR là một tập tin kiểu thực với mỗi phần tử của chúng là một số thực và có độ dài là 6 bytes.
- Biến tập tin FC là một tập tin kiểu ký tự với mỗi phần tử của chúng là một ký tự và có độ dài là 1 byte.
- Biến tập tin FP là một tập tin kiểu bản ghi với mỗi phần tử của chúng là một bản ghi và có độ dài là 67 bytes.
* Thủ tục ASSIGN(FileVar, FileName)
Lệnh này dùng để gán tên tập tin FileName cho 1 biến file là FileVar. Filename là tên của một tập tin, do vậy FileName cần tuân theo cách đặt tên của MS-DOS.
Ví dụ 8.48:
VAR Fnguyen : FILE OF integer ;
BEGIN
ASSIGN(Fnguyen, ‘SONGUYEN.DAT ’) ;
{ Gán tập tin có tên là SONGUYEN cho biến Fnguyen }
REWRITE(FileVar) {Cậu lệnh sẽ giải thích ở phần kế}
................
* Thủ tục REWRITE (FileVar)
Lệnh này mở một tập tin mới trên đĩa từ có tên là FileName (đã khai báo trong thủ tục ASSIGN). Nếu FileName đã có sẵn trong đĩa thì nội dung của nó bị xóa và con trỏ đặt ở đầu tập tin. Trong cả 2 trường hợp, sau lệnh này
trong tập tin đều rỗng và thông tin mới sẽ được đưa vào nhờ thủ tục WRITE sẽ trình bày ở phần sau.
* Thủ tục RESET(FileVar) Mở tập tin có sẵn trong đĩa từ có tên là FileName đã khai báo trong lệnh ASSIGN, con trỏ tập tin đặt ở đầu tập tin, dùng lệnh READ để đọc thông tin (sẽ đề cập ở phần sau).
* Thủ tục WRITE(FileVar, x1, x2, ..., xn) Lệnh này ghi lần lượt các biến x1, x2, ..., xn, là các biến thuộc kiểu phần tử của biến File, vào tập tin có tên là FileName (trong lệnh ASSIGN) vào đĩa từ theo ví trí tuần tự của con trỏ
đang đứng.
* Thủ tục READ(FileVar, x1, x2, ..., xn) Lệnh này đọc tuần tự các giá trị x1, x2, ..., xn tại vị trí con trỏ đang đứng rồi gán vào các biến tương ứng cùng kiểu.
Chú ý: Sau khi đọc hoặc ghi một phần tử, thì con trỏ tập tin sẽ di chuyển xuống vị trí phần tử tiếp theo.
Ví dụ 8.49:
Program Write_Read;
Var Tepnguyen: File Of integer;
i: integer;
Begin
Assign(tepnguyen,’U:\SONGUYEN.DAT ’);
Rewrite(Tepnguyen);
For i :=1 to 4 do write(tepnguyen,i);
begin
Read(tepnguyen,i);
write(‘i= ‘,i:4);
end;
Close(Tepnguyen);
Readln(tepnguyen);
End.
* Thủ tục SEEK(FileVar, n) Lệnh này chỉ thị con trỏ tập tin chuyển đến vị trí phần tử thứ n của tập tin. Trong đó, n là số nguyên chỉ vị trí các phần tử trong tập tin, phần tử thứ nhất có số thứ tự là 0.
* Hàm EOF(FileVar):Boolean
Hàm EOF (End-Of-File) này dùng để kiểm tra tình trạng hết tập tin. Nếu con trỏ ở cuối file thì nó sẽ cho kết quả là TRUE, ngược lại con trỏ ở bất kỳ nơi khác không phải ở cuối tập tin là FALSE.
Mỗi ô là một phần tử của tập tin. Nếu con trỏ nằm ở vị trí cuối, EOF sẽ là TRUE.
* Thủ tục CLOSE(FileVar) Ðóng tập tin lại và cập nhật thư mục để phản ánh tình trạng mới của tập tin.
3. Tập tin văn bản TOP
a. Khái niệm Trong Pascal có một kiểu tập tin khác, đó là tập tin văn bản, có tên gọi là text file. Ðây là tập tin kiểu ký tự, tuy nhiên, tập tin text được cấu trúc thành các dòng, mỗi dòng được kết thúc bởi dấu hết dòng eoln (end-
of-line).
Ðối với Turbo Pascal, dấu eoln được tạo bởi 2 ký tự điều khiển là CR (carriage return - về đầu dòng) và LF (line feed - nhảy xuống dòng tiếp). CR là chr(13) và LF là chr(10). Text file được kết thúc bởi dấu end-of-file, với Turbo
Pascal dấu end-of-file là Ctrl-Z có mã ASCII là ký tự số 26.
Vì chiều dài của các dòng là khác nhau nên tập tin văn bản chỉ có thể truy xuất theo kiểu tuần tự. Ngoài ra, không thể tiến hành cả 2 hoạt động đọc và ghi cùng lúc trên text file. Ðể sử dụng text file, ta phải thực hiện các thủ tục
ASSIGN, sau đó mở tập tin bằng các thủ tục RESET, REWRITE hoặc APPEND.
- REWRITE dùng để tạo ra một text file mới, sau đó ta chỉ có thể tiến hành ghi lên text file một cách tuần tự.
- RESET để mở một tập tin đã có và sau đó ta chỉ có thể đọc một cách tuần tự.
* Ghi dữ liệu vào tập tin văn bản
Có 3 mẫu để ghi dữ liệu vào text file là:
• WRITE(FileVar, Item1, Item2, ..., ItemN) ;
Ghi giá trị các phần tử Item1, Item2,..., ItemN lên tập tin được đại diện bởi FileVar.
• WRITELN(FileVar, Item1, Item2, ..., ItemN) ;
Giống như thủ tục WRITE, nhưng sau đó sẽ ghi thêm dấu kết thúc dòng (các ký tự điều khiển: CR và LF)
• WRITELN(FileVAR) ;
Chỉ làm một việc là ghi lên tập tin dấu kết thúc dòng.
FileVar là một biến kiểu text.
Item1, Item2, ..., ItemN là các mục ghi các hằng, biến, biểu thức của các kiểu vô hướng chuẩn, kiểu miền con và kiểu chuỗi.
Các thủ tục ghi vào text file không chấp nhận các kiểu ghi dữ liệu kiểu mảng (ARRAY), kiểu tập hợp (SET), kiểu bản ghi (RECORD) và kiểu tập tin (FILE).
Ví dụ
Program TepVanban;
Var f: TEXT;
Begin
Assign(f,’U:\Vanban.DAT’);
Rewrite(f);
(1) Writeln(f,1,2,3);
(2) write(f,4,5);
(3) writeln(f);
(4) Close(f);
End.
Chương trình sẽ tạo ra tệp U:\Vanban.Dat có dạng như sau:
* Ðọc dữ liệu từ tập tin văn bản:
Việc đọc dữ liệu từ tệp văn bản được thực hiện hoàn toàn tương tự như việc đọc dữ liệu từ bàn phím, ta có 3 mẫu để đọc dữ liệu từ text file là:
- READ(FileVar, Variable1, Variable2, ..., VariableN) ;
- READLN(FileVar, Variable1, Variable2, ..., VariableN) ;
- READLN(FileVAR) ;
FileVar là một biến kiểu text.
Variable1, Variable2, ..., VariableN là các biến thuộc các kiểu vô hướng chuẩn, kiểu miền con và kiểu chuỗi.
Thủ tục Readln(FileVar, Variable1, Variable2, ..., VariableN) ; sẽ đưa con trỏ xuống hàng tiếp sau khi đọc hết các biến tương ứng. Thủ tục Readln(FileVar) ; xuống dòng tiếp theo mà không đọc gì hết.
Các thủ tục đọc dữ liệu từ text file không chấp nhận các dữ liệu kiểu mảng (ARRAY), kiểu tập hợp (SET), kiểu bản ghi (RECORD) và kiểu tập tin (FILE).
Ví dụ Chương trình đếm số dòng của một tệp văn bản:
Program Dem_dong;
Var f:TEXT; sodong:integer;
Begin
sodong:=0;
{$I-}
Assign(f,’U:\VanBan.DAT’);
Reset(f);
{$I+}
if IOResult<>0 then
begin Writeln(' Tệp này không tồn tại '); halt(1); end
esle
begin
While Not EOF(f) Do
begin
INC(sodong);
readln(f); {Xuống dòng kế tiếp}
end;
Close(f);
Writeln(' Số dòng đã đếm được là :' ,sodong);
Readln;
End.
* Hàm EOLN(FileVar) Hàm EOLN(f) có giá trị là true nếu như con trỏ của text file f đang ở vào ký tự điều khiển CR của dấu end-of-line hoặc khi end-of-file có giá trị là true.
*
Dời qua bên trái k bit: SHL