JOJ 1106 Complicated Expressions

 

题目要求就是去除四则运算表达式中的冗余括号。这在做《编程之美》中的24点练习时就已经遇到过。方法很简单:先把中缀表达式转换成后缀表达式,这一步可以去掉所有的括号,然后把后缀表达式还原为中缀表达式,在还原的过程中加上必要的括号。有两种情况必须为子表达式加括号:一,子表达式的运算优先级低于根的运算优先级时,必须将子表达式用括号保护起来;二,如果根的运算符是减号或者除号,因为它们不满足交换律,这种情况下,假如右子表达式的运算符优先级和根一样时,也要把右子表达式用括号保护起来。

一点复习:

中缀表达式转后缀表达式的算法:

设置一个运算符栈。对中缀表达式从头到尾处理,遇到左括号直接入栈;遇到操作数直接将操作数放到后缀表达式尾部;遇到运算符,如果其优先级高于运算符栈顶元素的优先级则直接入栈,否则将栈顶元素取出放到后缀表达式尾部,反复直到遇到的运算符优先级高于栈顶运算符的优先级;遇到右括号,则不断将栈顶元素移到后缀表达式尾部,直到在栈顶遇到左括号,将左括号扔掉。

一点经验:

1.“程序员的技巧是至为重要的。例如,某些组合计算需要被重复1万亿次。因此如果我们能从它的内循环挤出1微秒来,就可以节约出11.6天来。”(TAOCP)。选定算法后,要为该算法编写尽可能高效的实现代码。(这个题目一开始使用了大量的stack, vector, string,提交成绩为0.9秒,使用C语言重写了一遍算法,提交成绩0.1)

2.不单对函数应该使用preconditionpostcondition,对于一个算法,最好是把它的流程图画出来,为流程图中的每个操作找到一组preconditionpostcondition。这个方法在经典算法书上都有,如TAOCP和《算法导论》中用于证明算法正确性使用的循环不变式技术。这个方法使人能彻底理解算法。在中缀表达式转后缀表达式算法中,在“当前分析到的运算符优先级不高于运算符栈顶元素的优先级时循环弹栈”以及到达中缀表达式的结束字符时把运算符栈中的所有运算符全部弹出至后缀表达式中,这两步操作就是为了保持状态的一致性。

3.《算法导论》中“指针和对象的实现”中介绍的对象的多重数组表示、对象的单数组表示、分配和释放对象等是极为实用的技术。这些技术能够在数组上实现动态数据结构,不仅免去了动态内存分配的开销,使得动态数据结构实现起来更加容易,而且丝毫不影响逻辑的清晰。在使用C语言实现后缀转中缀的算法时即使用了这种方法,这段代码如下:

void parse(const char* postfix, int len) { // 因为表达式不定长,并且需要动态合并表达式,所以如果为每个表达式分配 // 定长空间,则浪费应该很严重。所以所有的表达式都存储在result这片空间上 // result可以理解为表达式栈。start[i]表示第i个表达式的起始下标,end[i] // 表示结束下标,使用半开半闭区间,即第i个表达式长度为end[i]-start[i]。 // preStack为优先级栈,preStack[i]表示第i个表达式的优先级。 // cnt为当前产生的表达式个数。 char result[2048]; int start[256]; int end[256], preStack[256]; int i, cnt = 0, preLeft, preRight, p, k, t; start[0] = end[0] = 0; for(i = 0; i < len; i++) { if(isalpha(postfix[i])) { ++cnt; start[cnt] = end[cnt-1] + 5; result[start[cnt]] = postfix[i]; end[cnt] = start[cnt] + 1; preStack[cnt] = 10; } else { p = pre(postfix[i]); preLeft = preStack[cnt-1]; preRight = preStack[cnt]; // 为左表达式加括号 if(preLeft < p) { result[start[cnt-1] - 1] = '('; start[cnt-1] -= 1; result[end[cnt-1]] = ')'; end[cnt-1] += 1; } // 为右表达式加括号 if(preRight < p || (preRight == p && (postfix[i] == '-' || postfix[i] == '/'))) { result[start[cnt] - 1] = '('; start[cnt] -= 1; result[end[cnt]] = ')'; end[cnt] += 1; } // 合并两个表达式 result[end[cnt-1]] = postfix[i]; end[cnt-1] += 1; for(k = end[cnt-1], t = start[cnt]; t < end[cnt]; k++, t++) result[k] = result[t]; end[cnt-1] = k; cnt -= 1; preStack[cnt] = p; } } for(i = 1; i <= cnt; i++) { result[end[i]] = 0; printf("%s", result + start[i]); } printf("/n"); } 

下面是中缀表达式转换为后缀表达式的C代码:

int len = strlen(exp); char postfix[256], opStack[256]={'#'}, tmp; int top = 0, i, pEnd = -1; // infix to postfix for(i = 0; i < len; i++) { tmp = exp[i]; if(tmp == '(') opStack[++top] = '('; else if(isalpha(tmp)) postfix[++pEnd] = tmp; else if(strchr("+-*/", tmp)) { if(pre(tmp) > pre(opStack[top])) { opStack[++top] = tmp; } else { while(pre(tmp) <= pre(opStack[top])) { postfix[++pEnd] = opStack[top--]; } opStack[++top] = tmp; } } else { // tmp == ')' while(opStack[top] != '(') { postfix[++pEnd] = opStack[top--]; } top--; } } while(opStack[top] != '#') { postfix[++pEnd] = opStack[top--]; } postfix[++pEnd] = 0; 

 

你可能感兴趣的:(数据结构,编程,算法,vector,String,语言)