中缀表达式就是我们平时所看到的一般计算表达式,如:
①最简单的: 1 + 1
②稍微复杂一点的: 8 – ( 3 + 2 * 6 ) / 5 + 4
而后缀表达式(也被称为逆波兰式)则是将运算符放在了最后,两个操作数放在前面,以及不需要括号表示运算的优先级, 如:
①1 + 1 对应的就是 1 1 +
②8 – ( 3 + 2 * 6 ) / 5 + 4 对应的就是 8 3 2 6 * + 5 / – 4 +
详细逆波兰式的定义请参考百度百科
http://baike.baidu.com/link?url=7RYiuxHeWH7ikiVkP6s7RxwBrowWemzp_yL9MkqsBMd0Q3KAFB_35XexPBsq6g-ZvRoHE6MpQIaCzNbyMnI6Qq
OJ题可参考
http://codevs.cn/problem/2471/
现在当我们输入一个中缀表达式(不能有负数输入)的时候,我们通过两个栈来得到后缀表达式,这里我采用链表来实现栈,当然你也可以使用数组(更节省空间)。
typedef struct Node {
char element; //储存操作数或者运算符
struct Node *prev;
struct Node *next;
}Node;
typedef struct {
Node *top;
Node *base;
}Stack;
//以及栈的一些基本操作
//初始化栈,栈底设头结点,并以'#'为栈底标志
Stack *IntiStack(){
Stack *result = (Stack *)malloc(sizeof(Stack));
Node *headNode = (Node *)malloc(sizeof(Node));
headNode->prev = NULL;
headNode->next = NULL;
headNode->element = '#';
result->top = headNode;
result->base = headNode;
return result;
}
//将一个操作数或者运算符推进栈中
void Push(Stack *result, char element){
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->element = element;
newNode->prev = result->top;
newNode->next = NULL;
result->top->next = newNode;
result->top = newNode;
}
//获得栈中顶元素的值
char getTop(Stack *result){
return result->top->element;
}
//将栈中顶元素的值弹出,除非栈为空
char Pop(Stack *result){
char temp;
if(result->top != result->base){
temp = result->top->element;
result->top = result->top->prev;
result->top->next = NULL;
return temp;
}
return '#';
}
首先第一个栈temp是一个临时栈,负责储存运算符,并且该栈的头元素优先级最高(即从底到头优先级递增), 第二个栈result是结果栈, 从底到头储存了一个完整的后缀表达式。
中缀表达式转换为后缀表达式最为重要的就是当我碰到一个操作数或者运算符的时候应该怎么处理, 也就是优先级的确认。
①遇到操作数的时候直接推进result
②遇到运算符的时候需要比较该运算符与temp栈头元素的运算符的优先级,如果该运算符优先级高于(不包括等于)则将运算符推进temp, 否则弹出temp栈的头元素直到头元素的优先级低于该运算符,最后将该运算符推进temp中。
③当然有特殊情况,也就是遇到左括号“(”的时候直接推进temp, 当其他运算符与左括号比较优先级的时候可认为左括号的优先级最低。遇到右括号的时候,不断弹出栈的头元素直到遇到左括号,左括号弹出后不入result(后缀表达式不需要括号)
④接下来我们详细讨论每种情况:
“+”:优先级是最低的,几乎所有运算符都要弹出(包括加号自身),除了左括号。
“-”:与加号优先级相同,同上。
“*”:优先级稍高,只有遇到乘号,除号,以及幂号的时候才需要弹出。
“/”:与乘号优先级相同,同上。
“^”:优先级最高,只有遇到它自身的时候才需要弹出。
现在思路明确,我们上代码。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 300
int main(void){
char container[MAX];
scanf("%s", container);
int length = strlen(container);
Stack *temp = IntiStack();
Stack *result = IntiStack();
int i;
char top;
char newEle;
for(i = 0;i < length;i++){
switch(container[i]){
case '+':top = getTop(temp);
while(top == '*' || top == '/' || top == '+' || top == '-' || top == '^'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Push(temp, '+');
break;
case '-':top = getTop(temp);
while(top == '*' || top == '/' || top == '+' || top == '-' || top == '^'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Push(temp, '-');
break;
case '*':top = getTop(temp);
while(top == '*' || top == '/' || top == '^'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Push(temp, '*');
break;
case '/':top = getTop(temp);
while(top == '*' || top == '/' || top == '^'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Push(temp, '/');
break;
case '^':top = getTop(temp);
while(top == '^'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Push(temp, '^');
break;
case '(':Push(temp, '(');
break;
case ')':top = getTop(temp);
while(top != '('){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
Pop(temp);
break;
default:Push(result, container[i]);
}
}
//还需要将temp栈中剩下的符号都推入result栈中
top = getTop(temp);
while(top != '#'){
newEle = Pop(temp);
Push(result, newEle);
top = getTop(temp);
}
To Be Continued
现在result栈中从底到头就是一个完整的后缀表达式。OK,现在我们有一个完整的后缀表达式,那么接下来该怎么计算呢?
①我们可以在栈上面直接计算,当检测到一个运算符的时候计算它的前两个数字。
②通过一个完整的后缀表达式构造一棵二叉树,再通过后序遍历计算结点,将两个操作数合并成一个操作数并储存在运算符的位置。
(PS:先序遍历->前缀表达式, 中序遍历->中缀表达式, 后序遍历->后缀表达式)
①我们先看一下第一种方法,当我们要计算的时候,计算的数必定会超过10,所以用char来储存已经不能,我们需要一种新的结构体来储存每个符号,假定一个结点如果代表运算符,则它的操作数为-1,则每次根据操作数是否为负数判定该结点是否是运算符。
typedef struct INode {
char ope;
int operand;
struct INode *next;
}INode;
typedef struct {
INode *top;
INode *base;
}IStack;
然后我们接着给出接上面To be Continued的程序, 这里我没有封装它们的基本操作:
IStack *res = (IStack *)malloc(sizeof(IStack));
res->top = (INode *)malloc(sizeof(INode));
res->base = res->top;
Node *current = result->base->next;
INode *tempINode;
while(current){
tempINode = (INode *)malloc(sizeof(INode));
newEle = current->element;
if(newEle < 48 || newEle > 57){
tempINode->operand = -1;
tempINode->ope = newEle;
}
else{
tempINode->operand = newEle - '0';
}
tempINode->next = NULL;
res->top->next = tempINode;
res->top = tempINode;
result->base->next = current->next;
free(current);
current = result->base->next;
}
show(res);
while(res->base->next->next != NULL){
Calcu(res);
show(res);
}
return 0;
}
首先我们需要把之前的后缀表达式复制一次来构造新的结构的栈。
然后通过Calcu函数每次简单计算一次,直到计算出后缀表达式的最终值,show函数负责每次打印后缀表达式。
void Calcu(IStack *res){
INode *first, *second, *current, *preFirst;
preFirst = res->base;
first = preFirst->next;
second = first->next;
current = second->next;
while(current->operand != -1){
preFirst = preFirst->next;
first = first->next;
second = second->next;
current = current->next;
}
int temp;
switch(current->ope){
case '+':temp = first->operand + second->operand;
break;
case '-':temp = first->operand - second->operand;
break;
case '*':temp = first->operand * second->operand;
break;
case '/':temp = first->operand / second->operand;
break;
case '^':temp = Pow(first->operand, second->operand);
break;
}
free(first);
free(second);
INode *newNode = (INode *)malloc(sizeof(INode));
newNode->operand = temp;
newNode->next = current->next;
free(current);
preFirst->next = newNode;
}
void show(IStack *result){
INode *current = result->base->next;
while(current){
if(current->operand == -1){
printf("%c ", current->ope);
}
else{
printf("%d ", current->operand);
}
current = current->next;
}
printf("\n");
}
在Calcu函数中,从底层开始遍历,每遇到一个运算符就运算一次,并将运算符前两个节点合并。
②那么如果我们如果想构造一颗二叉树呢?
假设我们现在有一个后缀表达式,逐个的遍历。
如果是一个操作数则构造一个结点储存值(也就是一棵只有一个结点的树),并把它推进栈中。
如果是一个运算符,则弹出栈中的前两个结点分别构成它的左子树和右子树(注意,弹出的第一个结点是右子树, 第二个结点是左子树),然后将新结点推进栈中。
typedef struct TreeNode {
char element;
struct TreeNode *left;
struct TreeNode *right;
}TNode;
typedef struct {
TNode **container;
int top;
}TStack;
这里同样接着以上的To Be Continued, 这里的后序遍历结点计算合并的函数我省去了,跟上面差不多。
TStack *TS = (TStack *)malloc(sizeof(TStack));
TS->top = 0;
TS->container = (TNode **)malloc(sizeof(TNode *) * 200);
Node *current = result->base->next;
TNode *left, *right;
TNode *newNode;
show(result);
while(current){
newEle = current->element;
newNode = (TNode *)malloc(sizeof(TNode));
if(newEle == '+' || newEle == '-' || newEle == '/' || newEle == '*'){
right = TPop(TS);
left = TPop(TS);
newNode->element = newEle;
newNode->left = left;
newNode->right = right;
TPush(TS, newNode);
}
else{
newNode->element = newEle;
newNode->left = NULL;
newNode->right = NULL;
TPush(TS, newNode);
}
current = current->next;
}
return 0;
接下来我们看最后的一个问题,当我们给出一个中缀表达式的时候,其中有冗余的括号并要求我们去括号。
我们要先把中缀转后缀构建二叉树再中序遍历,其中中序遍历还有一点细微的处理, 因为我们还要把必要的括号加上。
void InOrder(TNode *node){
char current, left, right;
if(node == NULL){
return ;
}
else{
current = node->element;
if(node->left && node->right){
left = node->left->element;
right = node->right->element;
}
if((current == '*' || current == '/') && (left == '+' || left == '-' || right == '+' || right == '-' || right == '*' || right == '/')){
if(left == '+' || left == '-' ){
printf("(");
InOrder(node->left);
printf(")%c", node->element);
}
else{
InOrder(node->left);
printf("%c", node->element);
}
if(right == '+' || right == '-' || right == '*' || right == '/'){
printf("(");
InOrder(node->right);
printf(")");
}
else{
InOrder(node->right);
}
}
else{
InOrder(node->left);
printf("%c", node->element);
InOrder(node->right);
}
}
}
对于中序遍历的二叉树,左右子树的处理不一样:
①左子树是最先被遍历的,所以如果左子树的优先级低于(不包括等于)当前结点,则要先打印开括号,在左子树遍历结束后再打印闭括号。
②右子树是最后被遍历的,所以如果右子树的优先级低于或等于当前结点,则要在打印了当前结点之后加开括号,右子树遍历结束后打印闭括号。(当前结点是最后计算的, 如果右子树先算即使是同级也要添加括号)
完结撒花,这么长看下来大家辛苦了,欢迎指出不足之处。