数据结构与算法 - 3.3.3.2 后缀表达式

3.3.3.2 后缀表达式

假设咱们有一个便携计算器并须要计算一趟外出购物的花费。公式以下:
4.99 * 1.06 + 5.99 + 6.99 * 1.06 = ?
该例的典型计算顺序是: 1. 将4.99和1.06相乘并存入A1 2. 而后将5.99与A1相加,再将结果存入A1
3. 咱们再将6.99与1.06相乘并将答案存入A2
4. 最后将A1与A2相加并将结果放入A1
咱们能够将这种操做顺序书写以下:
4.99 1.06 * 5.99 + 6.99 1.06 * + 这种记法叫作后缀(prefix)或逆波兰式(reverse Polish)记法,其求值过程刚好就是咱们上述所描述的过程。计算这个问题最简单的方法就是使用一个栈。当见到一个数时就把它推入栈中;
在遇到一个运算符时将该运算符就做用在从该栈弹出的两个数(符号)上,将所得结果推入栈中,例如,后缀表达式:
*6 5 2 3 + 8 * + 3 + **
计算以下:前面四个字符放入栈中,此时栈变成了算法

Top  |     3      |
    ------->|------------|
            |     2      |
            |------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

下面读到一个“+”号,因此3和2从栈中弹出,而且他们的结果5被压入栈中。code

Top  |     5      |
    ------->|------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

接着,8进栈。it

Top  |     8      |
    ------->|------------|
            |     5      |
            |------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

如今见到一个“*”号,所以8和5弹出,而且8 * 5 = 40 进栈。io

Top  |    40      |
    ------->|------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

接着又见到一个“+”号,所以40和5弹出,而且40 + 5 = 45进栈。gc

Top  |    45      |
    ------->|------------|
            |     6      |
            +------------+

如今将3压入栈中。方法

Top  |     3      |
    ------->|------------|
            |    45      |
            |------------|
            |     6      |
            +------------+

而后“+”是的3和45弹出,并将3 + 45 = 48压入栈中。top

Top  |    48      |
    ------->|------------|
            |     6      |
            +------------+

最后,遇到一个“*”号,从栈中弹出48和6,将结果48 * 6 = 288压入栈中。di

Top  |    288     |
    ------->+------------+

计算一个后缀表达式花费的时间是O(N),所以对输入中的每一个元素的处理都是由一些栈操做组成从而花费常数时间。该算法的计算是很是简单的。
注意:当一个表达式之后缀记号给出时,没有必要知道任何优先规则。这是一个明显的优势。文件

中缀到后缀的转换

栈不只能够用来计算后缀表达式的值,并且咱们还能够用栈将一个标准形式的表达式(或叫中缀(infix))转换成后缀表达式。咱们经过只容许操做+,,(,),并坚持普通的优先级法则而将
通常的问题浓缩成小规模的问题。咱们还有进一步假设表达式是合法的。设咱们欲将中缀表达式
a + b * c + (d * e + f) * g
转换成后缀表达式。正确的答案是
a b c * + d e * f + g * +
当读到一个操做数的时候,当即把它放到输出中。操做符不当即输出,从而必须先存在某个地方。正确的作法是将已经见过的操做符放到栈中而不是输出中。当遇到左圆括号时咱们也要将其推入栈中。
咱们从一个空栈开始计算。
若是见到一个右括号,那么就将栈顶元素弹出,将弹出的符号写入直到咱们遇到一个(对应的)左括号。可是这个左括号只被弹出,并不输出。
若是咱们见到任何其余的符号(“+”,“
”,“(”),那么咱们将栈中弹出栈元素直至发现优先级更低的元素为止。有一个例外:除非是处理一个“)”的时候,不然咱们毫不从栈中移走“(”。
对于这种操做,“+”的优先级最低,而“(”的优先级最高。当从栈中弹出元素的工做完成后,咱们再将操做符压入栈中。
最后,若是咱们读到输入的末尾,咱们就将栈元素弹出直至该栈变成空栈,将符号写到输出中。
为了理解这种算法的运行机制,咱们将把上面的中缀表达式转换成后缀形式。
首先,a被读入,因而它流向输出。而后,“+”被读入并入栈。接着b被读入并流向输出。这一时刻的状态入下:时间

|            |              +---------------------------+                           
            |     +      |              | a b                       |
            +------------+              +---------------------------+
                 Stack                              Output

这时“”被读入,操做符栈顶元素“+”比“”的优先级低,故没有输出,“*”入栈。接着c被读入并流向输出。至此,咱们有

|     *      |
            |------------|              +---------------------------+                        
            |     +      |              | a b c                     |
            +------------+              +---------------------------+
                 Stack                              Output

后面的符号是一个“+”号,检查一下,咱们发现,须要将“*”从栈弹出并放到输出中;弹出栈中剩下的“+”,该运算符不比刚刚遇到的“+”号优先级低而是有相同的优先级,隐藏也被弹出病输出
而后,将刚刚遇到的“+”号放入栈中。

|            |              +---------------------------+                           
            |     +      |              | a b c * +                 |
            +------------+              +---------------------------+  
                 Stack                              Output

下一个被读到的符号是一个“(”,因为有最高的优先级,所以它被放入栈中。而后,d被读入并输出。

|     (      |
            |------------|             +---------------------------+                          
            |     +      |             | a b c * + d               |
            +------------+             +---------------------------+ 
                 Stack                              Output

继续进行,咱们又读到一个“*”。除非正在处理闭括号,不然开括号不会从栈中弹出,所以,没有输出,下一个是e,它被读入并输出。

|     *      |
            |------------|
            |     (      |
            |------------|             +---------------------------+                          
            |     +      |             | a b c * + d e             |
            +------------+             +---------------------------+
                 Stack                             Output

在日后咱们读到的符号是“+”号,咱们将“*”弹出,而后将“+”压入栈中。这之后,咱们读入f并输出。

|     +      |
            |------------|
            |     (      |
            |------------|             +---------------------------+                           
            |     +      |             | a b c * + d e * f         |
            +------------+             +---------------------------+  
                 Stack                             Output

如今,咱们读入“)”,所以将栈中元素直到“(”弹出,咱们将一个“+”号输出。

|            |              +---------------------------+                           
            |     +      |              | a b c * + d e * f +       |
            +------------+              +---------------------------+  
                 Stack                             Output

下面又读到一个“*”,该运算符被压入栈中。而后,g被读入并输出。

|     *      |
            |------------|              +---------------------------+                           
            |     +      |              | a b c * + d e * f + g     |
            +------------+              +---------------------------+  
                 Stack                              Output

如今输入为空,所以咱们将栈中全部符号都弹出并输出,直到栈变成空栈。

|            |              +---------------------------+                           
            |            |              | a b c * + d e * f + g * + |
            +------------+              +---------------------------+  
                 Stack                             Output

与前面相同,这种转换也只须要O(N)时间并通过一趟输入后工做完成。咱们能够经过指定减法和加法有相同的优先级以及乘法与除法有相同的优先级从而将减法与除法添加到指令集中。
一种巧妙的想法是将表达式“a - b - c”转换成“a b - c -”而不是转换成“a b c - -”

头文件

// 3.3.3.2 中缀转后缀
extern void Infix2Prefix(const char *);
// 是否为操做符
int IsOpSymbols(const char);
// 获取操做符优先级
int GetOpLevel(const char);
// 操做符优先级比对
int CompareOpLevel(const char, const char);

源代码

/**
 * 3.3.3.2 中缀转后缀
 * @todo
 * @param 
 */
void Infix2Prefix(const char *infix) {
	if (IsSymolsMatch(infix) == 0) {
		FatalError("The infix is not match.");
	}

	printf("The infix : %s\n", infix);
	printf("The prefix: ");

	Stack S = CreateStack();
	char curr, top;
	while (*infix) {
		curr = *infix;

		// 非操做符直接读入并流向输出
		if ( ! IsOpSymbols(curr)) {
			putchar(curr);
			infix++;
			continue;
		}

		// 空栈或“(”直接入栈
		if (IsEmptyStack(S) || curr == '(') {
			Push(curr, S);
			infix++;
			continue;
		}

		// 读入“)”,所以将栈中元素直到“(”弹出
		if (curr == ')') {
			do {
				if (IsEmptyStack(S)) {
					break;
				}

				top = Top(S);

				putchar(' ');
				putchar(top);
				Pop(S);
			} while(top == '(');

			Pop(S);
			infix++;
			continue;
		}

		// 操做符优先级比对
		do {
			if (IsEmptyStack(S)) {
				break;
			}

			top = Top(S);
			if ( ! CompareOpLevel(top, curr) || top == '(') {
				break;
			}

			putchar(' ');
			putchar(top);
			Pop(S);
		} while(CompareOpLevel(top, curr));

		Push(curr, S);
		infix++;
	}

	// 全部栈元素输出
	do {
		if (IsEmptyStack(S)) {
			break;
		}

		top = Top(S);

		putchar(' ');
		putchar(top);
		Pop(S);
	} while (top);
}

/**
 * 获取操做符优先级
 * @param op
 * @return 
 */
int GetOpLevel(const char op) {
	switch (op) {
		case '-':
		case '+':
			return 1;
			break;
		case '*':
		case '/':
			return 2;
			break;
		case '(':
			return 3;
			break;
	}
}

/**
 * 操做符优先级比对
 * @param c
 * @param s
 * @return 
 */
int CompareOpLevel(const char c, const char s) {
	return GetOpLevel(c) >= GetOpLevel(s);
}

/**
 * 是否为操做符
 * @param c
 * @return 
 */
int IsOpSymbols(const char c) {
	return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
}

调用示例 -- 中缀表达式转后缀表达式

#include <stdio.h>
#include <stdlib.h>

#include "ext/s15/stack.h"

int main(int argc, char** argv) {
	char str[] = "a + b * c + (d * e + f) * g";
	Infix2Prefix(str);

	return (EXIT_SUCCESS);
}
相关文章
相关标签/搜索