Thuật toán, biểu thức, số học
Trang 1Xử lý biểuthức số học
Lê Văn Chương
Trường THPT chuyên Phan Bội Châu - Nghệ An
Biểuthức số học được định nghĩa rất đơn giản và ngắn gọn như sau: Biểu thức số họclà một chuỗi ký tự bao gồm:
-Một số nguyên không dấu hoặc có dấu, hoặc một tên biến đặt theo chuẩn củaPascal -Hai biểu thức số học nối với nhau bởi một dấu phép tính (+, -, *, hoặc /) Cóthể đặt trong cặp dấu ngoặc đơn
Khi cho một biểu thứcthì thông thường ta phải kiểm tra xem biểu thức đã cho có là một biểu thức đúngkhông Để kiểm tra một biểu thức xem nó có đúng không ta cần kiểm tra xem cácsố của nó có đúng không?, các biến trong đó có được đặt tên theo chuẩn
củapascal không? các phép tính có hợp logic toán học không? các dấu ngoặc đơn cóđúng không?
Trong các điều kiệntrên thì kiểm tra số, tên biến, các dấu chúng ta không để cập đến vì chúng quádễ, một người mới bặt đầu học cũng có thể xử lý được Chúng ta chỉ quan tâm đếnxử lý các dấu phép tính và ngoặc đơn, đặc biệt là dấu ngoắc đơn bởi vì nó phứctạp và khó xử lý nhất
Bây giờ, chúng ta sẽxem xét một bài toán được gọi là đơn giản nhất trong xử lý biểu thức
số học.Nội dung của bài toán như sau:
Bài toán 1: Lập trình chươngtrình nhập vào một xâu ký tự chỉ gồm các dấu mở ngoặc và
đóng ngoặc đơn, sau đókiển tra tính đúng đăn của cách đặt dấu ngoặc Một xâu ký tự đúng là xâu thỏamãn:
- Số lần mở ngoặc đúng bằng số lần đóng ngoặc
- Dấu mở ngoặc phải đứng trước (ở phía bên trái)dấu đóng ngoặc tương ứng
Bài toán xử lý khôngkhó nhưng nó là nền tảng để chúng ta áp dụng vào các bài toán có mức độ caohơn Với bài này ta chỉ cần đọc dòng dữ liệu và tính số lần mở ngoặc có bằng sốlần đóng ngoặc không? nếu không thì đó là biểu thức sai, nếu có thì kiểm tratiếp dấu
mở ngoặc có đứng trước dấu đóng ngoặc tương ứng không Sau đây là đoạnchương trình chính
For i:=1 to length(s)do
Begin
Trang 2If s[i] in [ ( , ) ] then
Begin
If s[i]= ( then inc(mn)else dec(mn);
If mn<0 then beginWriteln( Bieu thuc sai );Halt;End;
End;
If mn<>0 thenbegin Writeln( Bieu thuc sai );Halt;End;
Writeln( Bieu thucdung );
End;
Chúngta có thể nâng cấp chúng trình trên để kiểm tra một biểu thức đẩy đủ nhập vàocó đúng không, đây chính là nội dung của bài toán 2
Bài toán 2: Cho một xâu ký tựhãy kiểm tra xem xâu ký tự đó có là một biểu thức số học
đúng hay không, biểuthức được xem là đúng nếu nó thỏa mãn các điều kiện sau:
- Một số nguyên, hoặc một tên biến đặt theochuẩn của pascal
- Hai biểu thức số học nối với nhau bởi một dấuphép tính (+, -, *, hoặc /), có thể đặt trong một cặp dấu ngoặc đơn
Với bài toán này, chúng ta không chỉkiểm tra dấu ngoặc đơn như bài trước mà còn kiểm tra tính đúng đắn của các phéptính, các số, các biến, kiểm tra các dấu phép tính có phù hợp không?
Thuật toán: Sử dụng biến dem đểkiểm tra các dấu ngoặc đơn có đúng không và biến dau
để kiểm tra tính đúng đắncủa dấụ Cách kiểm tra như sau:
Khởi tạo dem:=0 và dau:=1;
Gọi i là ký tự đang xét của xâu s:
- Nếu i là dấu '(' và biến dau có giá trị bằng 0thì đặt biến tăng biến dem lên 1;
- Nếu i là dấu ')' và trước i là một dấu phéptính thì chắc chắn đây là một biểu thức sai, ngược lại giảm biến dem đi 1 đểbáo cho chương trình biết đã có 1 cặp dấu mở ngoặc đúng; nếu dem<0 thì s làmột biểu thức sai bởi vì số ký tự đóng ngoặc nhiều hơn số ký tự mở ngoặc
Trang 3- Nếu i là một dấu phép tính(+, -, *, /) vàdau=1, có nghĩa là có 2 dấu liên tiếp nhau => đây là biểu thức sai, ngượclại, nếu i là một dấu phép tính và trước đó chưa có dấu nào (dau = 0) thì đặtdau:=1 để báo là đang xử lý biểu thức có dấu;
- Nếu i là các chữ cái A Z và a z thìđây chính là bắt đầu của 1 tên biến, ta phải xét i ở
vị trí cuối cùng của tênbiến này rồi xử lý tiếp:
- Nếu dau=0: S là biểuthức sai, bởi vì: ta khởi tao dau=1 nên với biến đầu tiên chương trình sẽ chonó đúng và gán lại dau=0 rồi xử lý tiếp, đến khi gặp một dấu phép tính khác thìmới khởi tạo dau=1 nên trong trường hợp này S là biểu thức sai
- Nếu dau=1: Gán lạidau=0 và xử lý tiếp các ký tự sau tên biến đó
Nếu i là các chữ số,ta phải xét vi trị mới của i là vị trí cuối cùng của số nguyên đó (nhiều số):
- Nếu dau=0: tương tựtrên, đây là biểu thức sai
- Nếu dau=1: gán lạidau=0 và xử lý các ký tự sau số vừa đọc
Dưới đây là thủ tục kiểm tra một xâu có làbiểu thức số học hay không:
Functiontest(s: String): boolean;
Vari,dem,dau: integer;
Begin
test:=false;
dem:=0;
dau:=1;
i:=1;
While (i<=length(s)) do
begin
Case s[i] of
'(' : begin If dau=0 then exit; inc(dem); end;
')' : begin
Trang 4If dau=1 then exit;
Dec(dem);
If dem<0 thenexit;
end;
'+','-','*','/' : If dau=1 then exit Else dau:=1;
'A' 'Z','á 'z': begin
If dau=0 then exit;
dau:=0;
While (i<LENGTH(S))AND(S[I+1]IN< p>
['á 'z','A' 'Z','0' '9']) do inc(i);
end;
'0' '9' : begin
If dau=0 then exit;
dau:=0;
While(i<>
['0' '9']) doinc(i);
end;
End;
Inc(i);
end;
If (dem=0)and(dau=0) then test:=true;
End;
Để kiếm tra một xâu s có là biểu thức số học haykhông ta chỉ cần gọi:
Trang 5If test(s) thenWriteln( La bieu thuc so hoc ) else Writeln( Bieu thuc sai );
Thôngthường sau khi kiểm tra tính đúng đắn của biểu thức thì người ra đề sẽ yêu cầu tính biểu thức đó với các giá trịbiến cho trước Có nhiều cách để tính giá trị biểu thức số học, sau đây tôi sẽgiới thiệu cho các bạn phương pháp Ký pháp nghịch đảo Balan
Ký pháp nghịch đảoBalan là một cách biểu diễn biểu thức toán học thông thường theo một dạng khác,khá thuận lợi cho việc tính toán một biểu thức trong máy tính
Một biểu thức toán họcthông thường có thể được biến đổi thành dãy gồm các toán tử và toán hạng, trongđó một toán tử được dùng để tính toán với 1 hoặc 2 toán hạng đứng trước nótrong dãy đó
Ví dụ: (T+R)*A biến đổi thành TR+A*
T+E*A biến đổi thành TEA*+
Có hai dạng ký pháp nghịch đảo Balan, preffix vàsuffix Dạng trên là suffix
Thuật toán để biến đổi biểu thức toán học thành dạng kýpháp nghịch đảo Balan như sau :
Ta sử dụng hai stack.Một dùng để lưu dạng Balan (nói tắt cho gọn), gọi là BtBalan, một dùng để lưucác toán tử dùng trong quá trình chuyển biểu thức thành dạng Balan, gọi làpheptinh Kết quả được đưa vào BtBalan Chỉ việc đọc lần lượt từ đầu đến cuốicủa BtBalan thì sẽ được dạng ký pháp nghịch đảo Balan của biểu thức đã cho
Ta sẽ đọc lần lượt từ đầu đến cuối biểu thức đãcho để xử lý:
- Nếu gặp dấu mở ngoặcthì Push nó vào stack pheptinh
- Nếu gặp một toánhạng thì Push nó vào stack BtBalan
- Nếu gặp một toán tửthì:
+ Nếu các toán tử đượclưu cuối cùng trong pheptinh có mức ưu tiên cao hơn thì lần lượt Pop các toántử đó ra khỏi pheptinh, Push nó vào BtBalan Làm vậy cho đến khi gặp phải toántử có mức ưu tiên bằng hoặc thấp hơn nó thì dừng
+ Push toán tử đangxét vào pheptinh
+ Mức ưu tiên của cáctoán tử được quy định như sau: + -, *, / Dấu mở ngoặc được coi là
có mức ưutiên thấp nhất
Trang 6- Nếu gặp dấu đóngngoặc thì Pop toàn bộ các toán tử được lưu trong pheptinh, Push vào BtBalan,cho đến khi gặp dấu mở ngoặc (được lưu trong pheptinh) Sau đó Pop luôn dấu mởngoặc đó ra, vứt đi
Sau khi đã thu đượcdạng kpnđ Balan rồi, tính toán như sau: tìm trong BtBalan một phép toán đứngliền sau một toán hạng Dùng phép toán đó áp dụng trên 1 hoặc 2 toán hạng liềntrước nó Thay một toán hạng bằng kết quả tìm được, xoá phần tử chứa toán hạngcòn lại(nếu là 2 toán hạng) và phần tử chứa toán tử ra khỏi BtBalan Tính đếnkhi không tìm được toán tử nào nữa thì thôi
Ta có bài toán tổng quát như sau: Cho xâu Slà biểu diễn của một biểu thức số học (với các định nghĩa biểu thức số học nhưtrên) Hãy kiểm tra xem biểu thức đã cho có hợp lý không? Nếu hợp lý thì tínhgiá trị biểu thức đã cho
Dữ liệu vào quy ước như sau:
- dòng đầu làbiểu thức cần tính toán
- Các dòng tiếptheo ghi giá trị các biến ở trong biểu thức cần tính (nếu có) theo cấu trúc:
=
Dữ liệu ra: tệp bieuthuc.out gồm 1 dòngghi biểu thức và giá trị của biểu thức đó sau khi tinh được
Ví dụ:
((me*1984)+(tra*1983))/us
me=24
tra=29
us=04
Dưới đây là chương trình đầy đủ và chính xác về kiểmtra biểu thức số học và tính giá trị biểu thức đó:
{$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V+,X+}
{$M16384,0,655360}
ProgramBieuthucsohoc;
Uses Crt;
Const
Trang 7MN = 200;
fi = 'bieuthuc.inp';
fo = 'bieuthuc.out';
Type
mangst = array [1 MN] of string[9]; manglg = array [1 MN] of longint; mangit = array [1 MN] of integer; Var
tenbien,ten : mangst;
Value,tri,stack : manglg;
p : mangit;
s,Hangso,Bienso : string;
so,sb,st,ip,sst,Sobien: longint; sym : char;
f,g : text;
ProcedureDocbien;
Var
s1: string;
i: integer;
Begin
Sobien:=0;
While not Eof(f) do
begin
Trang 8i:=1; Inc(Sobien);
Tenbien[Sobien]:='';
While s1[i]<>'=' do
begin
If s1[i]<>' ' thenTenbien[Sobien]:=Tenbien[Sobien]+UpCase(s1[i]); Inc(i);
end;
Value[Sobien]:=0;
While i<=length(s1) do
begin
If s1[i] in ['0' '9'] then
Value[Sobien]:=Value[Sobien]*10+ord(s1[i])-ord('0');
Inc(i);
end;
end;
For i:=1 to Sobien do
While length(tenbien[i])<8 dotenbien[i]:=tenbien[i]+' ';
End;
ProcedureLoi;
Begin
Writeln('LOI: Khong hop lý);
End;
Trang 9Var
i: integer;
Begin
Assign(f,fi); Reset(f);
Readln(f,s);
Docbien;
Close(f);
For i:=1 to length(s) dos[i]:=UpCase(s[i]); sb:=1; st:=0; ip:=0; sst:=0;
End;
ProcedureCach;
Begin
While s[sb]=' ' do Inc(sb);
End;
FunctionDoiso: longint;
Begin
so:=0;
While s[sb] in ['0' '9'] do
begin
so:=so*10+(ord(s[sb])-ord('0'));
Inc(sb);
end;
Trang 10ProcedureNhanbien;
Begin
Bienso:='';
While s[sb] in ['Á 'Z','0' '9'] do
begin
Bienso:=Biensơs[sb]; Inc(sb);
end;
While length(Bienso)<8 doBienso:=Biensớ '; End;
ProcedurePheptinh;
Begin
Cach;
Case s[sb] of
'(' : beginsym:=s[sb]; Inc(sb); end;
')' : begin sym:=s[sb]; Inc(sb); end;
'+' : begin sym:=s[sb]; Inc(sb); end;
'-' : begin sym:=s[sb]; Inc(sb); end;
'*' : begin sym:=s[sb]; Inc(sb); end;
'/' : begin sym:=s[sb]; Inc(sb); end;
'%' : begin sym:=s[sb]; Inc(sb); end;
'0' '9': begin sym:='0'; Doiso; end;
'Á 'Z': begin sym:='Á; Nhanbien; end;
Trang 11Else sym:=#0;
End;
End;
FunctionTimso: integer; Var
i: integer;
Begin
For i:=1 to st do
If ten[i]=' ' then
If tri[i]=so then
begin
Timso:=i;
exit;
end;
Inc(st); ten[st]:=' ';
tri[st]:=so;
Timso:=st;
End;
FunctionTimbien: integer; Var
i: integer;
Begin
For i:=1 to st do
Trang 12If pos(Bienso,ten[i])>0 then begin
Timbien:=i;
exit;
end;
Inc(st); ten[st]:='?'+Bienso; Timbien:=st;
End;
FunctionTimdau(c: char): integer; Begin
Case c of
'+':Timdau :=-1;
'-': Timdau:=-2;
'*': Timdau:=-3;
'/': Timdau:=-4;
'%': Timdau:=-5;
'#': Timdau:=-6;
End;
End;
ProcedureBieuthuc;
Var
i: integer; daubt: char;
Procedure Hangtu;
Trang 13i: integer;
Procedure Nhantu;
Var
i: integer;
Begin
While sym in ['0','A','('] do
begin
Case sym of
'0': begin i:=Timso; Inc(ip); p[ip]:=i; end; 'A': begin i:=Timbien;Inc(ip); p[ip]:=i; end; '(': begin
Pheptinh;Bieuthuc;
If sym<>')'then Loi;
end;
End;
Pheptinh;
end;
End;
Begin
Nhantu;
While sym in ['*','/','%'] do
begin
Trang 14Pheptinh;
Nhantu; Inc(ip); p[ip]:=i;
end;
End;
Begin
If sym in ['+','-'] then begindaubt:=sym; Pheptinh end Else daubt:='+';
Hangtu;
If daubt='-' then begin i:=Timdau('#');inc(ip); p[ip]:=i; end; While sym in ['+','-'] do
begin
daubt:=sym;
Pheptinh;
Hangtu;
i:=Timdau(daubt); Inc(ip); p[ip]:=i;
end;
End;
ProcedureTinh(i: integer);
Begin
Case p[i] of
-1: beginstack[sst-1]:=stack[sst-1]+stack[sst]; Dec(sst); end; -2: beginstack[sst-1]:=stack[sst-1]-stack[sst]; Dec(sst); end;
Trang 15-3: beginstack[sst-1]:=stack[sst-1]*stack[sst]; Dec(sst); end; -4: begin stack[sst-1]:=stack[sst-1]div stack[sst]; Dec(sst); end; -5: begin stack[sst-1]:=stack[sst-1]mod stack[sst]; Dec(sst); end; -6: stack[sst]:=-stack[sst];
End;
End;
ProcedureNapso(i: integer);
Begin
Inc(sst);
stack[sst]:=tri[p[i]];
End;
ProcedureHoiso(i: integer);
Var
j: integer;
Begin
Delete(ten[p[i]],1,1);
tri[p[i]]:=0;
For j:=1 to Sobien do
If tenbien[j]=ten[p[i]] then
begin
tri[p[i]]:=Value[j];
break;
end;
Trang 16End;
ProcedureTinhtoan;
Var
i: integer;
Begin
For i:=1 to ip do
begin
If p[i]<0 then Tinh(i)
Else
Case ten[p[i],1] of
' ': Napso(i);
'?': begin Hoiso(i); Napso(i);end; '&': Napso(i);
End;
end;
End;
ProcedureMain;
Begin
Pheptinh;
Bieuthuc;
Tinhtoan;
End;
Trang 17Var
i: integer;
Begin
Assign(g,fo); Rewrite(g);
Writeln(g,s,' = ',stack[1]);
Close(g);
End;
BEGIN
Clrscr;
Init;
Main;
Done;
END
Chươngtrình trên chưa hoàn chỉnh, bởi vì nó chưa tính được các giá trị hàm lượnggiác, hàm logarit, v v Nếu muốn có chương trình 4Đ (đầy đủ, đúng đắn!) cácbạn hãy liên hệ với tác giả theo địa chỉ email: chuong@bonbon.net