Chuyển biểu thức dạng trung tố ra dạng hậu tố tương ứng
Trang 1Mục lục 1
I Giới thiệu về đề tài: 2
1.2 ”KÝ PHÁP NGHỊCH ĐẢO BA LAN PHƯƠNG PHÁP TÍNH GIÁ TRỊ BIỂU THỨC TOÁN HỌC” 2
1.2.1 Các phương pháp biểu diễn phép toán hai ngôi: 2
1.2.2 Cây biểu diễn biểu thức 3
1.2.3 Thế nào là ký pháp nghịch đảo Ba Lan? 4
II Cấu trúc dữ liệu: 5
III Thuật toán cơ bản: 5
IV Mở rộng đề tài: 8
VI Kết quả chương trình: 40
VII Kết luận: 41
VIII Nhận xét của giáo viên hướng dẫn: 42
Trang 2I Giới thiệu về đề tài:
1.1 Tên đề tài: Chuyển biểu thức dạng trung tố ra dạng hậu tố tương ứng
1.2 ”KÝ PHÁP NGHỊCH ĐẢO BA LAN PHƯƠNG PHÁP TÍNH GIÁ TRỊ BIỂU THỨC TOÁN HỌC”
Khi lập trình, tính giá trị một biểu thức toán học là điều 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ươngtrì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ươngphá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
Để đơ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ảngtrắng nào giữa các ký tự
1.2.1 Các phương pháp biểu diễn phép toán hai ngôi:
Một phép toán hai ngôi trên tập hợp X là một ánh xạ f: X×X →X cho (a,b) f(a,b)
A Ánh xạ f khi đó thường được ký hiệu bởi *, được gọi là toán tử, các phần tử a, bđược gọi là các hạng tử (còn gọi là toán hạng)
Khi viết biểu thức biểu diễn phép toán đó ta có thể đặt ký hiệu toán tử ở trước, sauhoặc giữa các toán hạng (là biến hoặc hằng) Thông thường trong các biểu thức đại và sốhọc, ta viết kí hiệu phép toán giữa hai hạng tử( trung tố) Ví dụ : a +b, a * b, Khi mộtbiểu thức có nhiều phép toán, ta dùng các cặp dấu ngoặc "(", ")" và thứ tự ưu tiên cácphép toán để chỉ rõ thứ tự thực hiện các phép toán (Các phép toán đều quy về phép toán 2ngôi.)
Ta cũng có thể viết hai hạng tử trước và kí hiệu toán tử sau (hậu tố) Chẳng hạn:
a + b viết là a b +, a*b viết là a b *
Cũng có thể viết toán tử trước, hai toán hạng sau( tiền tố) Chẳng hạn:
Trang 3a + b viết là + a b, a * b viết là * a b
1.2.2 Cây biểu diễn biểu thức
Dùng cây biểu diễn biểu thức có thể thấy rõ trình tự tính toán biểu thức
Ví dụ: a*(b+c)-d^5
Được thực hiện theo sơ đồ biểu diễn bởi cây nhị phân sau:
-
/ \
* ^
/ \ / \
a + d 5 / \
b c Ta mô tả quá trình đọc, ghi nhận giá trị và thực hiện phép tính, giống như quá trình duyệt cây biểu thức theo thứ tự giữa như sau: 1 Đọc và ghi nhận giá trị biến a(con trái) 2 Đọc và ghi nhận loại phép toán * (con trái) 3 Đọc giá trị biến b (con trái) 4 Đọc và ghi nhận loại phép toán + (con phải) 5 Đọc giá trị biến c (con phải) thực hiện phép cộng b+c=x và ghi nhận kết quả x,thực hiện phép nhân a*x = y và ghi nhận kết quả y 6 Đọc và ghi nhận phép toán -7 Đọc giá trị biến d 8 Đọc và ghi nhận phép toán ^ 9 Đọc giá trị 5 và thực hiện phép lũy thừa z=d^5, thực hiện phép trừ k=y-z Để đơn giản ta giả sử các phép toán đều là hai ngôi Khi đó cây biểu thức là cây nhị phân đầy đủ Quy tắc thực hiện phép toán trên cây như sau: • Hoặc duyệt cây theo thứ tự giữa mỗi lần duyệt con phải nếu đã tính được giá trị tại con đó thì thực hiện phép tính quy định bởi toán tử ghi tại đỉnh cha Viết tuần tự các đỉnh duyệt theo thứ tự giữa thì ta có dãy
/ \ / \
* ^ + ^
/ \ / \ / \ / \
a + d 5 * c d 5
/ \ / \
Trang 4b c a b
a * b + c - d ^ 5 (1)
Theo cách này, mỗi khi duyệt một đỉnh
1 Nếu là lá bên trái (biến hoặc hằng) thì ghi nhận giá trị của biến
2 Nếu là toán tử thì lưu dạng của toán tử
3 Nếu là con phải và lá thực hiện phép tính theo toán tử đã lưu ở đỉnh cha giữa cácgiá trị đã lưu tại con phải và giá trị mới đọc tại con trái, ghi kết quả vào đỉnh cha
• Hoặc duyệt cây biểu thức trên theo thứ tự sau: Mỗi đỉnh được duyệt sau khi cả haiđỉnh con đã được duyệt Khi đó mỗi khi duyệt một đỉnh trong ta thực hiện toán tửghi tại đỉnh này với giá trị của hai con của đỉnh ấy
Thứ tự duyệt các đỉnh khi đó sẽ là:
a b c + * d 5 ^ - (2)
Khi duyệt theo thứ tự sau, việc thực hiện phép tính tiến hành theo quy tắc:
Mỗi khi thăm một đỉnh thì:
1 Nếu là lá thì ghi nhận giá trị của biến
2 Nếu là đỉnh trong thì thực hiện toán tử ghi ở đỉnh này vào các giá trị ghi ở hai đỉnhcon, ghi kết quả vào chính đỉnh này
Với dãy (1) nếu không dùng đến dấu ngoặc, có thể có hai cây biểu thức khác nhau chocùng một dãy đỉnh khi duyệt thứ tự giữa Còn với dãy (2) cùng với lưu ý rằng các biến vàhằng luôn được biểu diễn bằng các lá, các toán tử luôn biểu diễn bởi các nút trong, từ mỗi
dãy dạng (2) ta luôn dựng lại được cây biểu thức duy nhất theo giải thuật sau: Khi độ dài
dãy lớn hơn 1, duyệt từ bên trái sang, nếu gặp một phần tử là toán tử, thì lấy hai phần tử đứng trước nó ra khỏi dãy chuyển thành hai con của phần tử ấy (theo đúng thứ tự) Vì thế
biểu thức viết bởi dãy (2) là hoàn toàn xác định
1.2.3 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ể hiệ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, taphả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ậtngữ 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áchbiể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 haitoá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ởiCharles Hamblin - một triết học gia và khoa học gia máy tính người Úc - dựa theo côngtrình về ký pháp Ba Lan của nhà Toán học người Ba Lan Jan Łukasiewicz Hamblin trình
Trang 5bà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ứccô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áctoá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ễnlại thành 4 5 +
II Cấu trúc dữ liệu:
Ta xây dựng kiểu cấu trúc toantu theo kiểu stack (ngăn xếp) gồm các trường:
+ Trường tt kiểu char để chứa các toán tử và hàm.
+ Trường next kiểu con trỏ cấu trúc toantu chỉ đến phần tử kế tiếp.
struct toantu {
char tt;
toantu *next;
} ;
Kiểu cấu trúc thứ hai là hauto theo kiểu danh sách liên kết đơn gồm:
+ Trường letter kiểu mảng kí tự để chứa các kí hiệu của toán hạng, toán tử ,
hàm
+ Trường value kiểu double để chứa giá trị thực của các toán hạng.
+ Trường next kiểu con trỏ cấu trúc hauto để chỉ đến phần tử kế tiếp.
struct hauto {
char letter[10];
double value;
hauto *next;
};
typedef toantu *stack;
typedef hauto *list;
stack S;
list P;
III Thuật toán cơ bản:
Chuyển đổi từ trung tố sang hậu tố
Thuật toán chuyển đổi này được phát minh bởi vị giáo sư người Đức nổi tiếngEdsger 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) Thuậttoá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ểuthức từ trái sang phải:
- Nếu gặp một toán hạng (con số ) thì ghi nó vào chuỗi kết quả (chuỗi kết quả
là biểu thức trung tố)
- Nếu gặp dấu mở ngoặc, đưa nó vào stack
Trang 6- Nếu gặp một toán tử (gọi là o1 ), thực hiện hai bước sau:
* 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ả
* 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ả
Để dễ hiểu, bạn hãy quan sát quá trình thực thi của thuật toán qua một ví dụ cụ thể sau: Biểu thức cần chuyển đổi: 3+4*2/(1-5)
kết quả, và push / vào stack
ghi các toán tử pop được rahậu tố
-Pop tất cá các toán tử rakhỏi ngăn xếp và ghi vàohậu tố
void insert_last(char *x,double val);
* Hàm kiểm tra độ ưu tiên của các toán tử:
int ref(char x)
Trang 7void convert(char *expr)
{ char temp[10];
if(expr[i]=='(') push(expr[i]);
else if(expr[i]==')')
{ while(top()!='(')
Trang 8else
{
while(ref(top())>=ref(expr[i]))
{ temp[0]=pop();
temp[1]=’\0’;
insert_last(temp,1);
} push(expr[i]);
IV Mở rộng đề tài:
Như vậy phần thuật toán cơ bản đã nêu lên nguyên lý chung trong việc giải quyếtchuyển một biểu thức từ dạng trung tố sang hậu tố đối với các toán tử +,- ,*, / số có mộtchữ số Trong phần mở rộng này sẽ nói mở rộng hơn bao gồm cả số thực ( số có nhiềuchứ sô), các hàm mũ, sin, cos, tg, ln , việc kiểm tra một biểu thức có đúng không, và tínhtoán một biểu thức nếu biểu thức đúng
* Khi ta xét biểu thức có liên qua tới hàm tức là sẽ phải nhập các kí tự nên ta sẽ thêm vào trong hàm kiểm tra các kí tự nhập ở đầu vào như sau:
Trang 9* Theo các quy ước toán học, ta có được thứ tự ưu tiên của các phép toán
và các hàm Giai thừa (!) có độ ư tiên lớn nhất đến mũ (^) có độ ưu tiên kém hơn nhưng nó lại ưu tiên hơn so với hàm, kế tiếp sau hàm là các dạng toán tử một ngôi
mà ở đây ta dùng(#) để đánh dấu và các phép toán nhân (*) , chia (/) có độ ưu tiên hơn các phép toán (+) , trừ (-).
int ref(char x)
{
else if ( ( x=='*')||(x== '/')||( x=='%')) return 2;
else if( x=='#') return 3;
else if( x=='^') return 5;
else if( x=='!') return 6;
else if(x=='('||x==')') return 0;
case 'a': insert_last("sin",0); break;
case 'f': insert_last("lg",0); break;
case 'g': insert_last("sqrt",0); break;
case 'h': insert_last("exp",0); break;
case 'l': insert_last("acos",0); break;
0 không phải là toán hạng một ngôi
int test_am ( char *expr,int i)
Trang 10else if ( strcmpi(temp1,"sin") ==0 ) ok=1;
else if ( strcmpi(temp1,"sqrt")==0 ) ok=1;
else if ( strcmpi(temp1,"atan")==0 ) ok=1;
}
}
return ok;
}
* Hàm chuyển biểu thức trung tố sang hậu tố có mở rộng.
void convert(char *expr)
{
Trang 12if(strcmpi(temp,"pi")==0 ) insert_last("PI", 3.14);
else
{ if(strcmpi(temp,"sin")==0) x='a';
}
Trang 14while ( exp[i]==' ' && i<m )i++;
if( exp[i]=='%'||exp[i]=='!') return 0;
l=i-k-1;
while ( exp[l]==' ' && l>=0) l ;
if ( exp[l]=='%') return 0;
}
while ( exp[i]==' ' && i<m )i++;
while ( exp[i]==' ' && i<m )i++;
if (( test(exp[i])==1)||( test(exp[i])==3)|| exp[i]=='(') return 0;
if ( i==m) return 1;
} else if ( strcmpi(temp,"sin") ==0 ) ok=0;
else if ( strcmpi(temp,"cos") ==0 ) ok=0;
else if ( strcmpi(temp,"tg") ==0 ) ok=0;
else if ( strcmpi(temp,"cotg")==0 ) ok=0;
else if ( strcmpi(temp,"ln") ==0 ) ok=0;
else if ( strcmpi(temp,"lg") ==0 ) ok=0;
else if ( strcmpi(temp,"sqrt")==0 ) ok=0;
else if ( strcmpi(temp,"exp") ==0 ) ok=0;
else if ( strcmpi(temp,"asin")==0 ) ok=0;
else if ( strcmpi(temp,"acos")==0 ) ok=0;
else if ( strcmpi(temp,"atan")==0 ) ok=0;
else
{
ok=1;
if ( ok==1 ) return 0;
Trang 15while ( exp[i]==' ' && i<m )i++;
if ( test(exp[i])==2 && exp[i]!='+' && exp[i]!= '-') return 0;
} }
while ( exp[i]==' ' && i<m ){i++; l++;}
if ( k<1 && temp[k]=='!'&& (test(exp[i])==1||test(exp[i])==3||exp[i]=='('))
if( i==m && exp[i-1-l]!='!') return 0;
Trang 16+ Các hàm sau đây thực hiện nhiệm vụ tính toán một biểu thức:
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 Ý 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 đó.
* Xây dựng ngăn xếp tính kết quả từ biểu thức hậu tố nêu trên.
Trang 18push_T(s);
} else
Trang 21V Viết chương trình:
#include <stdio.h>
#include <conio.h>
Trang 22typedef toantu *stack;
typedef hauto *list;
{" Sin "," Cos "," Tg "," Cotg"," Lg "," Ln "},
{" asin"," acos"," atan"," Sqrt"," ( ) "," "},
void Khung ( int left , int top , int width, int height, int color );
void Khung_1( int left , int top , int width, int height, int color );
void write_string ( char *str, int x, int y, int color);
void keyboard();
Trang 23int test_am ( char *expr,int i);
void insert_last(char x,double val);
Trang 25write_string(" GVHD : Phan Thanh Tao",23, 17 ,11);
Trang 26write_string("Exit: Thoat",Left+Column*7+2,Row*4+2,14);
int width=5,height=1;
for (int i=0;i<Row;i++)
for (int j=0;j<Column;j++)
{
Khung_1(Left+j*(width+2),Top+i*(height+2),width,height,mau_khung);
write_string(btnControl[i][j],Left+j*(width+2)+1,Top+i*(height+2)+1,mau_phim); }
Trang 27else if( x=='#') return 3;
else if( x=='^') return 5;
else if( x=='!') return 6;
else if(x=='('||x==')') return 0;
Trang 28while ( exp[i]==' ' && i<m )i++;
if( exp[i]=='%'||exp[i]=='!') return 0;
l=i-k-1;
while ( exp[l]==' ' && l>=0) l ;
Trang 29if ( exp[l]=='%') return 0;
}
while ( exp[i]==' ' && i<m )i++;
while ( exp[i]==' ' && i<m )i++;
if (( test(exp[i])==1)||( test(exp[i])==3)|| exp[i]=='(') return 0;
if ( i==m) return 1;
} else if ( strcmpi(temp,"sin") ==0 ) ok=0;
else if ( strcmpi(temp,"cos") ==0 ) ok=0;
else if ( strcmpi(temp,"tg") ==0 ) ok=0;
else if ( strcmpi(temp,"cotg")==0 ) ok=0;
else if ( strcmpi(temp,"ln") ==0 ) ok=0;
else if ( strcmpi(temp,"lg") ==0 ) ok=0;
else if ( strcmpi(temp,"sqrt")==0 ) ok=0;
else if ( strcmpi(temp,"exp") ==0 ) ok=0;
else if ( strcmpi(temp,"asin")==0 ) ok=0;
else if ( strcmpi(temp,"acos")==0 ) ok=0;
else if ( strcmpi(temp,"atan")==0 ) ok=0;
else
{
ok=1;
if ( ok==1 ) return 0;
while ( exp[i]==' ' && i<m )i++;
if ( test(exp[i])==2 && exp[i]!='+' && exp[i]!= '-') return 0;
} }