Tải bản đầy đủ (.doc) (14 trang)

Cơ sở lý thuyết balan Biểu thức tiền tố, trung tố, hậu tố. Trình bày thuật toán và cài đặt trên c

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (587.88 KB, 14 trang )

TRƯỜNG ĐẠI HỌC KINH TẾ ĐÀ NẴNG


MÔN HỌC: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
GIÁO VIÊN: NGUYỄN THÀNH THỦY

ĐỀ TÀI:
CƠ SỞ LÝ THUYẾT BALAN:BIỂU THỨC TIỀN TỐ,TRUNG TỐ,HẬU
TỐ.TRÌNH BÀY THUẬT TỐN VÀ CÀI ĐẶT TRÊN C CHƯƠNG
TRÌNH TÍNH GIÁ TRỊ CỦA MỘT BIỂU THỨC TỐN HỌC TRÊN C

NHĨM THỰC HIÊN:NHĨM 10

1

Đà Nẵng 3/2011


I. CƠ SỞ LÝ THUYẾT KÝ PHÁP BALAN
I.1.

Ký pháp Ba lan

(tiếng Anh: Polish notation), còn gọi là ký pháp tiền tố (tiếng
Anh: prefix notation), là một cách viết một biểu thức đại số rất
thuận lợi cho việc thực hiện các phép toán. Đặc điểm cơ bản của
cách viết này là không cần dùng đến các dấu ngoặc và luôn thực
hiện từ trái sang phải.

Tác giả


Jan Łukasiewicz
Kí pháp Ba Lan do nhà logic toán Jan Łukasiewicz đề xuất khoảng năm 1920. Jan
Łukasiewicz là một nhà tốn học người Ba Lan. Ơng sinh ra ở Lwów, Galicia (nay là
Lviv, Ukraina)
Khi lập trình, tính giá trị một biểu thức tốn học là điều quá đỗi bình thường. Tuy
nhiên, trong nhiều ứng dụng (như chương trình vẽ đồ thị hàm số chẳng hạn, trong đó
chương trình cho phép người dùng nhập vào hàm số), ta cần phải tính giá trị của một
biểu thức được nhập vào từ bàn phím dưới dạng một chuỗi. Với các biểu thức tốn
học đơn giản (như a+b) thì bạn có thể tự làm bằng các phương pháp tách chuỗi “thủ
cơng”. Nhưng để “giải quyết” các biểu thức có dấu ngoặc, ví dụ như (a+b)*c +
(d+e)*f , thì các phương pháp tách chuỗi đơn giản đều không khả thi. Trong tình
huống này, ta phải dùng đến Ký Pháp Nghịch Đảo Ba Lan (Reserve Polish Notation –
RPN), một thuật toán “kinh điển” trong lĩnh vực trình biên dịch.

I.2. Thế nào là ký pháp nghịch đảo Ba Lan?
Cách trình bày biểu thức theo cách thông thường tuy tự nhiên với
con người nhưng lại khá “khó chịu” đối với máy tính vì nó không thể
2


hiện một cách tường minh q trình tính tốn để đưa ra giá trị
của biểu thức. Để đơn giản hóa q trình tính tốn này, ta phải
biến đổi lại biểu thức thông thường về dạng hậu tố - postfix
(cách gọi ngắn của thuật ngữ ký pháp nghịch đảo Ba Lan). Để
phân biệt hai dạng biểu diễn biểu thức, ta gọi cách biểu diễn biểu
thức theo cách thông thường là trung tố - infix (vì tốn tử
nằm ở giữa hai tốn hạng).
Ký pháp nghịch đảo Ba Lan được phát minh vào khoảng giữa thập
kỷ 1950 bởi Charles Hamblin - một triết học gia và khoa học gia
máy tính người Úc - dựa theo cơng trình về ký pháp Ba Lan của nhà

Tốn học người Ba Lan Jan Łukasiewicz. Hamblin trình bày nghiên
cứu của mình tại một hội nghị khoa học vào tháng 6 năm 1957 và
chính thức cơng bố vào năm 1962.
Từ cái tên hậu tố các bạn cũng đoán ra phần nào là theo cách
biểu diễn này, các toán tử sẽ được đặt sau các toán hạng. Cụ
thể là biểu thức trung tố: 4+5 sẽ được biểu diễn lại thành 4 5
+.
Q trình tính tốn giá trị của biểu thức hậu tố khá tự nhiên đối với
máy tính

I.3. Thế nào là biểu thức tiền tố, trung tố và hậu tố
Trong đoạn giới thiệu trên có lẽ bạn cũng hình dung được thế nào là biểu thức trung tố,
hiểu đơn giản tức là toán tử sẽ được đặt giữa hai toán hạng, dĩ nhiên đây phải là tốn tử
hai ngơi. Vậy dựa vào vị trí của của tốn tử, liệu ta có thể biểu diễn biểu thức đại số dưới
dạng khác? Câu trả lời là được, và như đã nói, ta có ba cách là biểu thức tiền tố (prefix),
trung tố (infix) và hậu tố (postfix). Hãy xem một chút giới thiệu về cách biểu diễn biểu
thức tiền tố và hậu tố để hiểu rõ hơn về chúng.
Prefix: Biểu thức tiền tố được biểu diễn bằng cách đặt toán tử lên trước các tốn
hạng. Cách biểu diễn này cịn được biết đến với tên gọi “ký pháp Ba Lan” do nhà toán học
Ba Lan Jan Łukasiewicz phát minh năm 1920. Với cách biểu diễn này, thay vì viết x+y như
dạng trung tố, ta sẽ viết +xy. Tùy theo độ ưu tiên của toán tử mà chúng sẽ được sắp xếp
khác nhau, bạn có thể xem một số ví dụ ở phía sau phần giới thiệu này.
Postfix: Ngược lại với cách Prefix, tức là các toán tử sẽ được đặt sau các toán hạng.
Cách biểu diễn này được gọi là “ký pháp nghịch đảo Ba Lan” hoặc được viết tắt
là RPN (Reverse Polish notation), được phát minh vào khoảng giữa thập kỷ 1950 bởi một
triết học gia và nhà khoa học máy tính Charles Hamblin người Úc.
Một số ví dụ:

Infix


Prefix

Postfix

X+y

+xy

xy+

X+y-z

-+xyz

xy+z -

X+y*z

+x*yz

xyz*+

X+(y-z)

+x-yz

xyz-+

3



II.

Phương pháp chuyển từ biểu thức trung tố sang tiền
tố và hậu tố

Thuật toán chuyển đổi này được phát minh bởi vị giáo sư người Hà
Lan nổi tiếng Edsger Dijkstra (cũng là tác giả của thuật tốn tìm
đường đi ngắn nhất được đặt theo tên ông và semaphore, một kỹ
thuật để đồng bộ các tiến trình trong lập trình đa nhiệm).

II.1.Thuật toán :
Thuật toán này cũng dựa theo cơ chế ngăn xếp. Ý tưởng chung của
thuật toán cũng là duyệt biểu thức từ trái sang phải:
- Nếu gặp một toán hạng (con số hoặc biến) thì ghi nó vào chuỗi
kết quả (chuỗi kết quả là biểu thức hậu tố).
- Nếu gặp dấu mở ngoặc, đưa nó vào stack.
- Nếu gặp một toán tử (gọi là o1 ), thực hiện hai bước sau:
o Chừng nào cịn có một tốn tử o2 ở đỉnh ngăn xếp VÀ độ
ưu tiên của o1 nhỏ hơn hay bằng độ ưu tiên của o 2 thì
lấy o2 ra khỏi ngăn xếp và ghi vào kết quả.
o Push o1 vào ngăn xếp
- Nếu gặp dấu đóng ngoặc thì cứ lấy các tốn tử trong ngăn xếp
ra và ghi vào kết quả cho đến khi lấy được dấu mở ngoặc ra
khỏi ngăn xếp.
- Khi đã duyệt hết biểu thức trung tố, lần lượt lấy tất cả toán tử
(nếu có) từ ngăn xếp ra và ghi vào chuỗi kết quả.

II.2. Ví dụ tổng quát:
Q=a*(b+c)-d^5

Kí hiệu biểu thức ghi dưới dạng phép tốn sau là P. Trong q trình chuyển đổi ta
dùng một stack S để lưu các phần tử trong P chưa sử dụng đến. Khi đọc từ trái sang
phải biểu thức Q la lần lượt có:
1.
2.
3.
4.
5.

dấu ngoặc mở "(", đưa dấu ngoặc này vào stack: S = "*(".
Đọc hạng tử b, đưa b vào P: P= "a b"
Đọc toán tử "+", đặt "+" vào stack: S ="*(+"
Đọc hạng tử "c", đưa c vào cuối P: P="a b c"
Đọc dấu ngoặc đóng ")". Lần lượt lấy các tốn tử ở cuối stack ra khỏi stack đặt
vào cuối P cho đến khi gặp dấu ngoặc mở "(" trong stack thì giải phóng nó: S=
"*"; P="a b c +" Đọc và ghi nhận giá trị a, ghi giá trị a vào P. Vậy P = "a".
4


6. Đọc toán tử "*". Đưa toán tử này vào stack S: S = "*"
7. Đọc
8. Đọc toán tử "-". Cuối stack S có tốn tử "*" có mức ưu tiên lớn hơn toán tử
"-", ta lấy toán tử "*" ra khỏi stack, đặt vào cuối P, đặt toán tử "-" vào stack:
S="-"; P=" a b c + * "
9. Đọc hạng tử d, đưa d vào cuối P. P="a b c + * d"
10. Đọc toán tử "^", đưa toán tử "^" vào cuối stack: S="-^"
11. Đọc hằng số 5, đưa 5 vào cuối P: P="a b c + * d 5"
12. Đã đọc hết biểu thức Q, lần lượt lấy các phần tử cuối trong stack đặt vào P cho
đến hết. P="a b c + * d 5 ^ -".


II.3.Ví dụ cụ thể:
Biểu thức cần chuyển đổi: 3+4*2/(1-5)

Vậy biểu thức hậu tố là: 3 4 2 * 1 5 - / +

Tính giá tri biểu thức khi đã biến đổi về
dạng hậu tố.

III.

III.1.

Thuật toán:

5


Ý tưởng là đọc biểu thức từ trái sang phải, nếu gặp một tốn
hạng (con số hoặc biến) thì push toán hạng này vào ngăn
xếp; nếu gặp toán tử, lấy hai tốn hạng ra khỏi ngăn xếp
(stack), tính kết quả, đẩy kết quả trở lại ngăn xếp. Khi quá
trình kết thúc thì con số cuối cùng cịn lại trong ngăn xếp
chính là giá trị của biểu thức đó.

III.2.

Ví dụ:

biểu thức trung tố :
3+4*2/(1-5)

được biểu diễn lại dưới dạng hậu tố là (ta sẽ bàn về thuật toán
chuyển đổi từ trung tố sang hậu tố sau):
342*15-/+

Q trình tính tốn sẽ diễn ra theo như bảng dưới đây:
Ký tự
Thao tác
Trạng thái stack
3
Push 3
3
4
Push 4
3, 4
2
Push 2
3, 4, 2
*

6


Tính 4*2
Push 8
3,8
1
Push 1
3, 8, 1
5
Push 5

3,8,1,5
Tính 1-5
Push -4
3,8,-4
/
Tính 8/-4
Push -2
3,-2
+
Tính 3+(-2)
Push 1
1
Vậy kết quả bằng 1

7


IV.

Cài đặt giải thuật bằng chương trình C:

IV.1.

Yêu cầu bài tốn:

Cài đặt trên C chương trình tính giá trị biểu thức:chương trình
đọc dữ liệu từ file INPUT.TXT chứa các biểu thức toán học hợp
lệ,đọc file,xử lý cà ghi ra file OUTFUT.TXT giá trị của các biểu
thức tương ứng
Ví dụ:

File INFUT.TXT:
(6+4*(2+6))/2
5-(3+2)*8
File OUTPUT.TXT
19
-35
Chương trình xử lý:
#include<stdio.h>
#include<conio.h>
#include<string.h>
#include<ctype.h>
#define max 50
FILE *f1,*f2;
//khoi tao stack
void khoitaostack(int &t)
{
t=-1;
}
//ham bo phan tu vao stack
void push(char s[],int &t,char x)
{
if(t==max)
{
//printf("stack day");
return;
}
t++;
s[t]=x;
}
//ham stack rong

int emptystack(int t)
8


{
return(t==-1);
}
//ham stack day
int fullstack(int t)
{
return(t==max);
}
// ham lay ptu tu stack
void pop(char s[],int &t,char &x)
{
if (emptystack(t))
{
//
printf("Empty Stack !!!");
return;
}
x=s[t];
t=t-1;
}
// ham kiem tra do uu tien cua cac phep toan
int uutien (char ch)
{
switch(ch)
{
case '/' : return(2);

case '*' : return(2);
case '+' : return(1);
case '-' : return(1);
}
return(0);
}
// ham kiem tra toan tu
int toantu( char c)
{
switch(c)
{
case '/' : return(1);
case '*' : return(1);
case '+' : return(1);
case '-' : return(1);
return(0);
}
}
// ham chuyen tu trung to sang hau to
//------------------------------------//----------------------------------------void convert (char P[],char Kq[])
{
char x,ch,S[50];
9


int t1,t2,leng=strlen(P);
khoitaostack(t1);
khoitaostack(t2);
// duyet tu trai sang phai cac ptu cua bieu thuc trung to P
for(int i=0;i

{
ch=P[i];
if(isdigit(ch))
push(Kq,t2,ch);// kiem tra toan hang (chi ktra co phai la so)
else if(ch=='(') push(S,t1,ch);
else if (toantu(ch)==1) // ktra neu la toan tu
{
if(uutien(ch)<=uutien(S[t1]))
{
pop(S,t1,x);
push(Kq,t2,x);
}
push(S,t1,ch);
}
else if (ch==')')
{
// lan luot lay cac toan tu trong Stack S cho den khi gap '('
while(S[t1] !='(')
{
pop(S,t1,x);
push(Kq,t2,x);
}
pop(S,t1,x);
}
}
// lan luot lay cac toan tu con lai (neu co) trong S cho vao Kq khi da duyet xong P
while(!emptystack(t1))
{
pop(S,t1,x);
push(Kq,t2,x);

}
push(Kq,t2,'\0');
}
// cac ham tinh bieu thuc hau to.
//-------------------------------//---------------------------------void push2(int s[],int &t,int x) // stack nay mang gia tri kieu nguyen de tinh toan
{
t++;
s[t]=x;
}
void pop2(int s[],int &t,int &x)
{
10


x=s[t];
t--;
}
// ham tinh bieu thuc hau to
void tinhBTHT(char A[])
{
int S[max],t=-1,x1,x2;// khoi tao mot stack
char ch;
for(int i=0;i{
ch=A[i];
if(isdigit(ch))
push2(S,t,ch-'0');// dua gtri nguyen vao stack
else// khi gap toan tu
{
// lay 2 ptu cuoi stack hien thoi, tinh ket qua,roi cho vao lai stack

pop2(S,t,x1);
pop2(S,t,x2);
switch(ch)
{
case '+' : push2(S,t,x2+x1); break;
case '-' : push2(S,t,x2-x1); break;
case '*' : push2(S,t,x2*x1); break;
case '/' : push2(S,t,x2/x1); break;
}
}
}
fprintf(f2,"%d\n",S[0]);
printf("\n ket qua la:%d\n",S[0]);
}
// ham chinh
main()
{
char Kq[max],P[max];
f1=fopen("d:\\input.txt","rt");
f2=fopen("d:\\output.txt","wt");
//printf("/n moi ban nhap vao mot chuoi");
while(!feof(f1))
{
fscanf(f1,"%s\n",P);
convert(P,Kq);
printf("\n ------------------------------------------------");
printf("\n ket qua khi chuyen tu trung to sang hau to: %s",Kq);
tinhBTHT(Kq);
}
fclose(f1);

fclose(f2);
getch();
11


}
Chú ý:Chương trình này chỉ áp dụng cho 1 chữ số và chưa kiểm tra tính đúng đắn
của đầu vào Để đơn giản cho việc minh họa, ta giả định rằng chuỗi
biểu thức mà ta nhận được từ bàn phím chỉ bao gồm: các dấu mở
ngoặc/đóng ngoặc; 4 tốn tử cộng, trừ, nhân và chia (+, -, *, /);
các toán hạng đều chỉ là các con số nguyên từ 0 đến 9; khơng có
bất kỳ khoảng trắng nào giữa các ký tự.
Dĩ nhiên là thuật tốn được trình bày ở đây là khá đơn giản và chưa
ứng dụng được trong trường hợp biểu thức có các hàm như sin, cos,
… hoặc có các biến. Tuy nhiên, việc mở rộng thuật tốn là hồn
tồn nằm trong khả năng của bạn nếu bạn đã hiểu cặn kẽ thuật
toán cơ bản này.
File input.txt:

Kết quả:

12


In kết quả ra file output.txt:

13


14




×