• 字符串表达式计算器的设计


    思路

    1. 将通常的中缀表达式(Infix Notation)转化为后缀表达式(Postfix Notation)
    2. 计算后缀表达式

    后缀表达式的计算

    比如中缀表达式9 + (3 - 1) * 3 + 10 / 2

    转化成后缀表达式为9 3 1 - 3 * + 10 2 / +

    具体计算步骤为:

    1. 9 2 3 * + 10 2 / +
    2. 9 6 + 10 2 / +
    3. 15 10 2 / +
    4. 15 5 +
    5. 20

    计算时,遍历后缀表达式,碰到操作符后,就取符号前面的两个数字作为操作数,计算完后再把结果放回去,如此下来,直到全部算完只剩下一个数,就是结果。后缀表达式相较于中缀表达式更适合计算机计算,不需要括号,只依靠顺序来确定运算符优先级。


    如何将中缀表达式转化为后缀表达式?

    A方法

    从头到尾读取中缀表达式的每一个对象,对不同对象按不同的情况处理。

    1. 运算数:直接输出;
    2. 左括号:压入堆栈;
    3. 右括号将栈顶的运算符弹出输出直到遇到左括号(出栈,不输出);
    4. 运算符
    • 优先级大于栈顶运算符,则压入堆栈
    • 优先级小于等于栈顶运算符,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
    1. 若各对象处理完毕,则把堆栈中存留的运算符一并输出

    思考一下为什么要这么做

    用计算机的视角去看待这个问题:

    我:输入a

    计算机:a :a是操作数吧

    (于是输出a)

    我:输入+

    计算机: a +: 知道了,+号的左操作数是a

    (+入栈)

    我:输入b

    计算机:a+b:+号的右操作数还不能断定是b,看下一个符号的情况吧

    我:输入*

    计算机:a+b*:ok,*号的左操作数是b

    我:输入c

    计算机:a+b*c:*号的右操作数是c

    我:输入-

    计算机:a+b*c-:b*c是-的左操作数

    我:输入d,回车

    计算机:a+b*c-d,d是-的右操作数

    我:怎么感觉和递归好像...

    B方法

    例如表达式a + b * c - (d + e)

    1. 按照运算符优先级对所有运算单位加括号

    ((a + (b * c)) - (d + e))

    1. 将括号内的运算符移到括号外右边

    ((a (b c)*) + (d e)+)-

    1. 去掉括号,即得到了后缀表达式

    a b c * + d e + -

    具体实现(C语言)

    该函数返回符号对应的优先级

    int getPriority(char sign)
    {
    	switch(sign) {
    		case '+':
    		case '-': return 1;
    		case '/':
    		case '*': return 2;
    		case '^': return 3;
    		case '(': return 0;
    		default:  return -1;
    	}
    }
    

    该函数将中缀表达式转化为后缀表达式,使用了链式结构的堆栈。因为要返回一个新的字符串,所以使用了static修饰符,需要注意的是就是如果第一次用temp保存返回的结果,再次调用transform函数之后,temp指向的内容也会发生变化。还有就是如果出现了'-',那么它既有可能是单目的取反符号,也有可能是双目的减号,需要判断一下。如果是单目的取反符,那么就要在它前面补上0使之成为减法运算。

    char* transform(const char infix[])
    {
    	unsigned cnt = 0;
    	static char ret[MAX];
    	Stack signs = CreateStack();
    	for (; *infix != ''; ++infix) {
    		switch(*infix) {
    			case '0':case '1':case '2':
    			case '3':case '4':case '5':
    			case '6':case '7':case '8':
    			case '9':case '.':
    			ret[cnt++] = *infix;break;
    			
    			case '-':
    				if (cnt == 0 || !isdigit(*(infix-1))) {
    					ret[cnt++] = '0';
    				}
    			case '+':case '*':case '/':case '^':
    				ret[cnt++] = ' ';
    				while (!StackIsEmpty(signs) && getPriority(*infix) <= getPriority(StackTop(signs)))
    				{
    					ret[cnt++] = PopStack(signs);
    					ret[cnt++] = ' ';
    				}
    			case '(':
    				PushStack(signs, *infix);break;
    
    			case ')':
    				ret[cnt++] = ' ';
    				while (StackTop(signs) != '(') {
    					ret[cnt++] = PopStack(signs);
    					ret[cnt++] = ' ';
    				}
    				PopStack(signs);break;
    		}			
    	}
    	ret[cnt++] = ' ';
    	while (!StackIsEmpty(signs)) {
    		ret[cnt++] = PopStack(signs);
    		ret[cnt++] = ' ';
    	}
    	ret[cnt] = '';
    	DeleteStack(signs);
    	return ret;
    }
    

    该函数计算后缀表达式的值,使用了顺序结构的堆栈。先将后缀表达式按空格分割成单个的运算数或符号。再进行遍历,遇到数字便进栈,遇到符号便取出栈顶的两个元素计算后放回堆栈。遍历完之后,栈顶元素就是计算结果。

    double compute(const char *postfix)
    {
    	
    	double stack[MAX], op1, op2;
    	char sequence[MAX][16];
    	unsigned cnt = 0;
    	int top = -1;
    	char *sq = sequence[cnt]; 
    	const char *pf = postfix;
    	for (; *pf; ++pf)
    		if (*pf != ' ') {
    			for (; *pf != ' '; *sq++ = *pf++);
    			*sq = '';
    			sq = sequence[++cnt];
    		}
    
    	sq = sequence[0];
    	for (unsigned i = 0; i < cnt; sq = sequence[++i]) {
    		if (isdigit(sq[0])) stack[++top] = atof(sq);
    		else {
    			op1 = stack[top-1], op2 = stack[top];
    			top = top - 2;
    			switch (sq[0]) {
    				case '+': stack[++top] = op1 + op2; break;
    				case '-': stack[++top] = op1 - op2; break;
    				case '*': stack[++top] = op1 * op2; break;
    				case '/': stack[++top] = op1 / op2; break;
    				case '^': stack[++top] = pow(op1, op2); break;
    			}
    		}
    		
    	}
    	return stack[0];
    }
    

    一些小收获

    在C的函数里,函数内是无法获取数组长度的,只能在传入参数是给定长度,不过,通过宏可以实现调用函数时无需传入数组长度。

    void func_(int n[], int len);
    #define func(n) func_((n), sizeof(n)/sizeof(n[0]))
    
  • 相关阅读:
    模板复习
    [BZOJ4016][FJOI2014]最短路径树问题(dijkstra+点分治)
    Stirling数,Bell数,Catalan数,Bernoulli数
    [BZOJ2820]YY的GCD
    [BZOJ2154]Crash的数字表格
    [HAOI2011]Problem b&&[POI2007]Zap
    [BZOJ2588][SPOJ10628]Count on a tree
    [ONTAK2010]Peaks
    [HNOI2010]弹飞绵羊
    [HNOI2004]宠物收养所
  • 原文地址:https://www.cnblogs.com/ark2000/p/10584302.html
Copyright © 2020-2023  润新知