波兰式与逆波兰式

1 波兰式

波兰式是在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之前,所以,这种表示法也称为前缀表达式。例如:3*(2-(5+1)),用波兰式来表示是:* 3 - 2 + 5 1。

阅读这个表达式需要从左至右读入表达式,如果一个操作符后面跟着两个操作数时,则计算,然后将结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。从左往右依次读取,直到遇到+ 5 1,做计算后,将表达式替换为* 3 - 2 6,然后再次从左往右读取,直到遇到- 2 6,做计算后,将表达式替换为*3 (-4)(这里“-”为负号不是减号,-4为一个数负四),从而得到最终结果-12。

2. 逆波兰式

逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)。举个简单的例子,平常我们写的数学表达式a+b,就是一种中缀表达式,写成后缀表达式就是ab+。再举一个复杂的例子,中缀表达式(a+b)*c-(a+b)/e的逆波兰式是ab+c*ab+e/-。
例如:3*(2-(5+1)),用逆波兰式来表示是:3 2 5 1 + - *,也就是把操作运算符往操作数后面放。

阅读这个表达式需要从左往右读入表达式,当读到第一个操作符时,从左边取出两个操作数做计算,然后将这个结果作为操作数替换这个操作符和两个操作数,重复此步骤,直至所有操作符处理完毕。
3 2 5 1 + - *
→3 2 6 - *
→3 (-4) *
→ -12

3 中缀表达式与波兰式、逆波兰式的转换

既然中缀表达式对于计算机的运算并不便利,而前缀后缀表达式的计算相对简单方便。因此,找到一种途径将中缀表达式转换成前缀后缀表达式就十分重要。实际上,二者的转换算法看起来也很像一个逆过程。
相信看完前面写的,大家应该发现了,前缀表达式就是操纵符基本上都分布在操作数的前面,而后缀表达式的操作符又基本上都分布在操作数的后面,那我介绍一种终极简单易懂的转换规律。
同样我们以3*(2-(5+1))举例,当我们在计算中缀表达式的时候,我们只需要把所有优先级用括号标注起来,然后再根据波兰式和逆波兰式,前者把操作符提前,后者提后,再把括号去掉:
3*(2-(5+1))

波兰式 逆波兰式
→(3*(2-(5+1))) → (3*(2-(5+1)))
→*(3-(2+(5 1))) → (3(2(5 1)+)-)*
→* 3 - 2 + 5 1 → 3 2 5 1 + - *

转逆波兰式的例子:
波兰式与逆波兰式_第1张图片

4 中缀表达式转逆波兰式算法

波兰式与逆波兰式_第2张图片

 1 char *RPExpression(char *e)
 2 /* 返回表达式e的逆波兰式 */
 3 {
 4     //栈s1用于存放运算符,栈s2用于存放逆波兰式
 5     Stack s1,s2;
 6     InitStack(s1);
 7     InitStack(s2);    
 8     
 9     //假设字符'#'是运算级别最低的运算符,并压入栈s1中
10     Push(s1,'#');
11     
12     //p指针用于遍历传入的字符串,ch用于临时存放字符,length用于计算字符串长度 
13     char *p=e,ch;
14     int length=0;
15     for(;*p!='\0';p++)//逐个字符访问
16     {
17         switch(*p)
18         {
19             //遇'('则直接入栈s1
20             case '(':
21                 Push(s1,*p);
22                 break;
23             //遇')'则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次送入栈s2,此时抛弃'('
24             case ')':
25                 while(Top(s1)!='(')
26                 {
27                     Pop(s1,ch);
28                     Push(s2,ch);
29                 }
30                 Pop(s1,ch);
31                 break;
32             //遇下列运算符,则分情况讨论:
33             //1.若当前栈s1的栈顶元素是'(',则当前运算符直接压入栈s1;
34             //2.否则,将当前运算符与栈s1的栈顶元素比较,若优先级较栈顶元素大,则直接压入栈s1中,
35             //  否则将s1栈顶元素弹出,并压入栈s2中,直到栈顶运算符的优先级别低于当前运算符,然后再将当前运算符压入栈s1中
36             case '+':
37             case '-':
38                 for(ch=Top(s1);ch!='#';ch=Top(s1))
39                 {                
40                     if(ch=='(')
41                     {                        
42                         break;
43                     }
44                     else
45                     {
46                         Pop(s1,ch);
47                         Push(s2,ch);                        
48                     }                                          
49                 }
50                 Push(s1,*p);
51                 length++;
52                 break;
53             case '*':
54             case '/':
55                 for(ch=Top(s1);ch!='#'&&ch!='+'&&ch!='-';ch=Top(s1))
56                 {                
57                     if(ch=='(')
58                     {                        
59                         break;
60                     }
61                     else
62                     {
63                         Pop(s1,ch);
64                         Push(s2,ch);
65                     }                                          
66                 }
67                 Push(s1,*p);
68                 length++;
69                 break;
70             //遇操作数则直接压入栈s2中
71             default:
72                 Push(s2,*p);   
73                 length++;             
74         }
75     }    
76     //若栈s1非空,则将栈中元素依次弹出并压入栈s2中
77      while(!StackEmpty(s1)&&Top(s1)!='#')
78     {
79         Pop(s1,ch);
80         Push(s2,ch);        
81     }
82     //最后将栈s2输出,逆序排列成字符串;
83     char *result;
84     result=(char *)malloc(sizeof(char)*(length+1));
85     result+=length;
86     *result='\0';
87     result--;
88     for(;!StackEmpty(s2);result--)
89     {
90         Pop(s2,ch);
91         *result=ch;        
92     }
93     ++result;
94     return result;
95 }

其实s2不必是栈,是栈反而更复杂,直接输出即可。或者是个队列就行。

下面这个描述可能更清晰一些,但是图片丢失了:原文链接中缀表达式转换为后缀表达式
规则
中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f + g * +。

转换过程需要用到栈,具体过程如下:
1)如果遇到操作数,我们就直接将其输出。
2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。
3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) “的情况下我们才弹出” ( “,其他情况我们都不会弹出” ( "。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。

实例
规则很多,还是用实例比较容易说清楚整个过程。以上面的转换为例,输入为a + b * c + (d * e + f)*g,处理过程如下:

1)首先读到a,直接输出。

2)读到“+”,将其放入到栈中。

3)读到b,直接输出。

此时栈和输出的情况如下:

4)读到“*”,因为栈顶元素"+“优先级比” * " 低,所以将" * "直接压入栈中。

5)读到c,直接输出。

此时栈和输出情况如下:

6)读到" + “,因为栈顶元素” * “的优先级比它高,所以弹出” * “并输出, 同理,栈中下一个元素” + “优先级与读到的操作符” + “一样,所以也要弹出并输出。然后再将读到的” + "压入栈中。

此时栈和输出情况如下:

7)下一个读到的为"(",它优先级最高,所以直接放入到栈中。

8)读到d,将其直接输出。

此时栈和输出情况如下:

9)读到" * “,由于只有遇到” ) “的时候左括号”(“才会弹出,所以” * "直接压入栈中。

10)读到e,直接输出。

此时栈和输出情况如下:

11)读到" + “,弹出” * “并输出,然后将”+"压入栈中。

12)读到f,直接输出。

此时栈和输出情况:

13)接下来读到“)”,则直接将栈中元素弹出并输出直到遇到"(“为止。这里右括号前只有一个操作符”+"被弹出并输出。

14)读到" * ",压入栈中。读到g,直接输出。

15)此时输入数据已经读到末尾,栈中还有两个操作符“*”和" + ",直接弹出并输出。

至此整个转换过程完成。程序实现代码后续再补充了。

5 总结

为什么要将看似简单的中序表达式转换为复杂的波兰式或者逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。就比如你用用中文说“你是谁”,英语是“Who are you",直接翻译过来就是”谁是你“,这就是不同的语法规则造成的。所以我们在与计算机交流的时候要用计算机语言。

原文链接:

  1. 波兰式与逆波兰式
  2. 【数据结构及算法】1.将表达式转换成逆波兰式
  3. 中缀表达式转换为后缀表达式

你可能感兴趣的:(数据结构与算法,数据结构)