[Demo] Reverse Polish Notation
Thế nào là biểu thức tiền tố, trung tố và hậu tố
- 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.
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-+ |
Phương pháp chuyển từ biểu thức trung tố sang tiền tố và hậu tố
Độ ưu tiên của các toán tử
1
2
3
4
5
6
7
8
| public static int GetPriority( string op) { if (op == "*" || op == "/" || op == "%" ) return 2; if (op == "+" || op == "-" ) return 1; return 0; }
|
Định dạng lại biểu thức Infix trước khi chuyển đổi
1
2
3
4
5
6
7
8
9
10
| public static void FormatExpression( ref string expression) { expression = expression.Replace( " " , "" ); expression = Regex.Replace(expression, @"\+|\-|\*|\/|\%|\^|\)|\(" , delegate (Match match) { return " " + match.Value + " " ; }); expression = expression.Replace( " " , " " ); expression = expression.Trim(); } |
1
2
3
| expression = Regex.Replace(expression, @"\+|\-|\*|\/|\%|\^|\)|\(" , match => String.Format( " {0} " , match.Value) ); |
Các phương thức kiểm tra toán tử và toán hạng
Trong thuật toán chuyển đổi này ta cần có các phương thức kiểm tra xem một thành phần của chuỗi có phải là toán tử hoặc toán hạng không. Thay vì sử dụng các cấu trúc if hoặc switch dài dòng và bất tiện khi phát triển, ta sẽ dùng Regex để kiểm tra.Ngoài ra vì chuỗi nhập vào là một biểu thức đại số, nên các toán hạng ta sẽ xét không chỉ là các chữ số mà còn có chữ cái từ a-z và A-Z.
Có một quy tắc nữa là khi dùng chữ cái thì chỉ cho phép duy nhất một chữ cái đại diện cho một toán hạng, còn khi dùng chữ số thì có thể nhiều chữ số ghép thành một toán hạng.
1
2
3
4
5
6
7
8
| private static bool IsOperator( string str) { return Regex.Match(str, @"\+|\-|\*|\/|\%" ).Success; } public static bool IsOperand( string str) { return Regex.Match(str, @"^\d+$|^([a-z]|[A-Z])$" ).Success; } |
Chuyển biểu thức Infix sang Postfix
Thuật toán để chuyển một biểu thức Infix sang dạn Prefix:
Hãy xem ví dụ sau để hiểu rõ hơn thuật toán này.- Nếu là toán hạng: cho ra output.- Nếu là dấu mở ngoặc “(“: cho vào stack- Nếu là dấu đóng ngoặc “)”: lấy các toán tử trong stack ra và cho vào output cho đến khi gặp dấu mở ngoặc “(“. (Dấu mở ngoặc cũng phải được đưa ra khỏi stack)- Nếu là toán tử:
- Chừng nào ở đỉnh stack là toán tử và toán tử đó có độ ưu tiên lớn hơn hoặc bằngtoán tử hiện tại thì lấy toán tử đó ra khỏi stack và cho ra output.
- Đưa toán tử hiện tại vào stack
Sau khi duyệt hết biểu thức infix, nếu trong stack còn phần tử thì lấy các token trong đó ra và cho lần lượt vào output.
Token | Stack | Output |
A | {Empty} | A |
* | * | A |
B | * | A B |
+ | + | A B * |
C | + | A B * C |
* | + * | A B * C |
( | + * ( | A B * C |
( | + * ( ( | A B * C |
D | + * ( ( | A B * C D |
- | + * ( ( - | A B * C D |
E | + * ( ( - | A B * C D E |
) | + * ( | A B * C D E - |
+ | + * ( + | A B * C D E - |
F | + * ( + | A B * C D E – F |
) | + * | A B * C D E – F + |
/ | + / | A B * C D E – F + * |
G | + / | A B * C D E – F + * G |
{Empty} | A B * C D E – F + * G / + |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| public static string Infix2Postfix( string infix) { FormatExpression( ref infix); IEnumerable< string > str = infix.Split( ' ' ); Stack< string > stack = new Stack< string >(); StringBuilder postfix = new StringBuilder(); foreach ( string s in str) { if (IsOperand(s)) postfix.Append(s).Append( " " ); else if (s == "(" ) stack.Push(s); else if (s == ")" ) { string x = stack.Pop(); while (x != "(" ) { postfix.Append(x).Append( " " ); x = stack.Pop(); } } else // IsOperator(s) { while (stack.Count > 0 && GetPriority(s) <= GetPriority(stack.Peek())) postfix.Append(stack.Pop()).Append( " " ); stack.Push(s); } } while (stack.Count > 0) postfix.Append(stack.Pop()).Append( " " ); return postfix.ToString(); } |
Chuyển biểu thức Infix sang Prefix
Chuyển biểu thức Infix A*B+C*((D-E)+F)/G sang dạng Prefix
Đọc từng token trong biểu thức infix từ trái qua phải, với mỗi token ta thực hiện các bước sau:- Nếu là toán hạng: cho ra output.- Nếu là dấu đóng ngoặc “)“: cho vào stack- Nếu là dấu mở ngoặc “(”: lấy các toán tử trong stack ra và cho vào output cho đến khi gặp dấu đóng ngoặc “)“. (Dấu đóng ngoặc cũng phải được đưa ra khỏi stack)- Nếu là toán tử:
- Chừng nào ở đỉnh stack là toán tử và toán tử đó có độ ưu tiên lớn hơn toán tử hiện tại thì lấy toán tử đó ra khỏi stack và cho ra output.
- Đưa toán tử hiện tại vào stack
Sau khi duyệt hết biểu thức infix, nếu trong stack còn phần tử thì lấy các token trong đó ra và cho lần lượt vào output. Cuối cùng là đảo ngược biểu thức một lần nữa và ta sẽ thu được kết quả.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| public static string Infix2Prefix( string infix) { List< string > prefix = new List< string >(); Stack< string > stack = new Stack< string >(); FormatExpression( ref infix); IEnumerable< string > enumer = infix.Split( ' ' ).Reverse(); foreach ( string s in enumer) { if (IsOperand(s)) prefix.Add(s); else if (s == ")" ) stack.Push(s); else if (s == "(" ) { string x = stack.Pop(); while (x != ")" ) { prefix.Add(x); x = stack.Pop(); } } else // if (IsOperator(s)) { while (stack.Count > 0 && GetPriority(s) < GetPriority(stack.Peek())) prefix.Add(stack.Pop()); stack.Push(s); } } while (stack.Count > 0) prefix.Add(stack.Pop()); StringBuilder str = new StringBuilder(); for ( int i = prefix.Count - 1; i >= 0; i--) { str.Append(prefix[i]).Append( " " ); } return str.ToString(); } |
Một số điểm lưu ý
Ví dụ bạn có thể tính x+y-z bằng cách nhóm chúng lại thành
(x+y)-z (1)
hoặc
x+(y-z) (2)
Thông thường chúng ta ưu tiên cách tính số (1) từ trái qua.
Điểm tạo nên sự khác biệt là cách chúng ta so sánh độ ưu tiên của các toán tử, ví dụ thay vì viết dấu “<” trong đoạn mã này, ta có thể sửa thành “<=”, giá trị của chúng vẫn không thay đổi:
//…
while (stack.Count > 0 && GetPriority(s) < GetPriority(stack.Peek()))
prefix.Add(stack.Pop());
stack.Push(s);
//…
Kiểm tra kết quả
Để kiểm tra kết quả có chính xác không, bạn có thể dùng một dịch vụ chuyển đổi trực tuyến tại địa chỉ http://scriptasylum.com/tutorials/infix_postfix/infix_postfix.html. Trang web cũng sẽ thực hiện tính toán giá trị của biểu thức sau khi chuyển đổi. Như kết quả của biểu thức dưới đây là 111.
0 nhận xét:
Đăng nhận xét
Click to see the code!
To insert emoticon you must added at least one space before the code.