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
Trang 1TRƯỜ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 TOÁN VÀ CÀI ĐẶT TRÊN C CHƯƠNG TRÌNH TÍNH GIÁ TRỊ CỦA MỘT BIỂU THỨC TOÁN HỌC TRÊN C
NHÓM THỰC HIÊN:NHÓM 10
Trang 2I 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à toá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 toá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 toá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ể
Trang 3hiện một cách tường minh quá trình tính toán để đưa ra giá trị
của biểu thức Để đơn giản hóa quá trình tính toá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ì toán tử nằm ở giữa hai toá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à Toá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 +
Quá trình tính toá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à toán tử hai ngôi Vậy dựa vào vị trí của của toá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 toá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ụ:
Trang 4II 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 toá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 toá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 o2 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 toá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 toán sau là P Trong quá 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 dấu ngoặc mở "(", đưa dấu ngoặc này vào stack: S = "*(".
2 Đọc hạng tử b, đưa b vào P: P= "a b"
3 Đọc toán tử "+", đặt "+" vào stack: S ="*(+"
4 Đọc hạng tử "c", đưa c vào cuối P: P="a b c"
5 Đọc dấu ngoặc đóng ")" Lần lượt lấy các toá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".
Trang 56 Đọ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ó toá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 - / +
dạng hậu tố.
III.1 Thuật toán:
Trang 6Ý tưởng là đọc 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ì push toán hạng này vào ngăn xếp; nếu gặp toán tử, lấy hai toá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):
3 4 2 * 1 5 - / +
Quá trình tính toá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
*
Trang 7Tí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
Trang 8IV Cài đặt giải thuật bằng chương trình C:
IV.1 Yêu cầu bài toá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)
Trang 9{
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[])
Trang 10int 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<leng;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)
{
Trang 11x=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<strlen(A);i++) // duyet tu trai sang phai cac ptu cua hau to {
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);
Trang 12Chú ý: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 toá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 toá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 toán là hoàn toà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ả:
Trang 13In kết quả ra file output.txt: