今天为了复习栈和队列的相关知识,所以尝试着用c写了一段求解中缀表达式的程序。主要分两步实现:一是完成中缀表达式到后缀表达式的转换,二是实现中缀表达式的求解。
用栈可以比较方便地将中缀表达式转化为后缀表达式,举个例子,如果我们从控制台输入这样一个中缀表达式:12+5*(4+2*3)-6,那么它对应的后缀表达式就是12 5 4 2 3 * + * + 6 -。那么,我们怎样用栈实现这个中缀表达式到它所对应的后缀表达式的转换呢?首先我们需要定义一个栈,用于转换表达式,另外我们还需要一个队列来保存转换结果,以便进行下一步的求解。值得注意的是从控制台输入的表达式是保存在缓冲区的,而缓冲区中实际上依次存储这样的一串字符:‘1’ ’2‘ ’+‘ ’5‘ ’*‘ ’(‘ ’4‘ ’+‘ ’2‘ ’*‘ ’3‘ ’)‘ ’-‘ ’6‘ ’\n'。用getchar()依次读取字符,接下去的操作是这样的:
- ‘1’ ’2‘是数字,直接入队列;
- 发现’+‘,因为是第一个运算符所以入栈保存;
- ’5‘是数字 ,直接入队列,入队列;
- 发现 ’*‘,因为 ’*‘的优先级比‘+’高,所以‘5’不是‘+’的右操作数,要先知道‘*’的又操作数才能知道‘+’的右操作数,‘*’入栈;
- 发现‘(’,无条件入栈,在遇到‘)’之前我们都不知道‘*’的右操作数是谁。
- ‘4’是数字,入队列;
- 发现’+‘,因为是‘(’后的第一个运算符所以入栈保存;
- ‘2’是数字,入队列;
- 发现‘*’,因为’*‘的优先级比‘+’高,所以‘2’不是‘+’的右操作数,‘*’入栈;
- ‘3’是数字,入队列;
- 发现‘)’,说明‘3’确实是’*‘的右操作数,依次输出取走栈顶的操作符,放入队列中,直到发现‘(’,‘(’只出栈,不入队列;
- 发现‘-’,因为栈顶是’*‘,比‘-’优先级高,说明前面的一整坨是’*‘的右操作数,‘*’出栈,入队列,接着‘+’出栈,入队列,栈已空,‘-’入栈;
- ‘6’是数字,入队列;
- 发现‘\n’,说明输入结束,输出栈中的所有运算符,‘-’出栈,入队列。
这样,队列中保存的就是逆波兰表达式,值得注意的是,在发现预算符和‘\n’之后,要在队列中插入一个空格,因为数与数之间要用空格加以区分,还有在每个运算符出栈之后,也要在队列中插入空格,以隔开不同的运算符。下面总结一下具体的处理规则:
- 发现数字,直接入队列;
- 发现’+‘或‘-’,将依次取出栈顶元素,入队列,直到遇到‘(’或栈底;
- 发现’*‘或’/‘,如果栈顶元素是’*‘或’/‘,则取出栈顶元素,入队列,直到栈顶元素不是’*‘或’/‘为止;
- 发现‘(’,直接入栈;
- 发现‘)’,依次取出栈顶元素,入队列,直到遇到‘(’;
- 发现‘\n’,输出栈中的所有运算符,直到栈底。
while(1)
{
inputChar = getchar();
if(inputChar >= '0' && inputChar <= '9')
{
insertQueue(cQueue, inputChar);
}
else if('\n' == inputChar)
{
insertQueue(cQueue, ' ');
while(cStack ->top != cStack ->base)
{
popChar(cStack, &outputChar);
insertQueue(cQueue, outputChar);
insertQueue(cQueue, ' ');
}
break;
}
else if('*' == inputChar || '/' == inputChar)
{
insertQueue(cQueue, ' ');
while('*' == *(cStack ->top) || '/' == *(cStack ->top))
{
popChar(cStack, &outputChar);
insertQueue(cQueue, outputChar);
insertQueue(cQueue, ' ');
}
pushChar(cStack, inputChar);
}
else if('+' == inputChar || '-' == inputChar)
{
insertQueue(cQueue, ' ');
while('(' != *(cStack ->top) && cStack ->top != cStack ->base)
{
popChar(cStack, &outputChar);
insertQueue(cQueue, outputChar);
insertQueue(cQueue, ' ');
}
pushChar(cStack, inputChar);
}
else if('(' == inputChar)
{
pushChar(cStack, inputChar);
}
else if(')' == inputChar)
{
insertQueue(cQueue, ' ');
while(1)
{
if('(' == *(cStack ->top))
{
popChar(cStack, &outputChar);
break;
}
if(cStack ->base == cStack ->top)
{
break;
}
popChar(cStack, &outputChar);
insertQueue(cQueue, outputChar);
insertQueue(cQueue, ' ');
}
}
}
值得注意的就是空格的插入,得到的逆波兰表达式保存在cQueue指向的队列中。
接下去的步骤就是依次取出cQueue队列中队头的元素,计算队列中保存的逆波兰表达式。这里要用另一个栈来计算逆波兰表达式的结果,依然使用前面的例子,我们在上一步中得到的逆波兰表达式为12 5 4 2 3 * + * + 6 -,这个表达式是怎么计算的呢?首先,从左往右读取每个元素,读到第一个运算符是‘*’,计算2*3=6,下一步,读到‘+’,计算4+6=10,下一步,读到‘*’,计算5*10=50,下一步,读到‘+’,计算12+50=62,然后读到数字6,跳过,读到最后一个运算符‘-’,计算62-6=56,ok,最终结果就是56。我们发现,这种从后往前的计算方式和栈的处理惊人地相似。所以我们完全可以定义一个栈来处理这个问题,处理的策略如下:
- 凡是读到数字直接推到栈中(注意队列中的数字用空格隔开);
- 如果读到操作符,取出栈底元素,那么该元素就是运算符的右操作数,再次取出栈底元素,那么该元素就是运算符的左操作数,计算运算结果,然后压入栈中;
- 如果逆波兰表达式是正确的,那么当队列清空之后,栈中只剩一个元素,那么这个元素就是我们的计算结果。
inputInt = 0;
while(1)
{
if(cQueue ->front == cQueue ->rear)
{
popInt(iStack, &tempInt1);
if(iStack ->base != iStack ->top)
{
printf("输入表达式有问题!!!\n");
return 0;
}
printf("计算结果为: %d\n", tempInt1);
return 0;
}
deleteQueue(cQueue, &inputChar);
if((inputChar >= '0') && (inputChar <= '9'))
{
while((inputChar >= '0') && (inputChar <= '9'))
{
inputInt = 10 * inputInt + (int)(inputChar - '0');
deleteQueue(cQueue, &inputChar);
}
pushInt(iStack, inputInt);
inputInt = 0;
}
else if(inputChar == '+')
{
if(!popInt(iStack, &tempInt2) || !popInt(iStack, &tempInt1))
{
printf("输入的表达式有问题!!!\n");
return 0;
}
pushInt(iStack, tempInt1 + tempInt2);
}
else if(inputChar == '-')
{
if(!popInt(iStack, &tempInt2) || !popInt(iStack, &tempInt1))
{
printf("输入的表达式有问题!!!\n");
return 0;
}
pushInt(iStack,tempInt1 - tempInt2);
}
else if(inputChar == '*')
{
if(!popInt(iStack, &tempInt2) || !popInt(iStack, &tempInt1))
{
printf("输入的表达式有问题!!!\n");
return 0;
}
pushInt(iStack,tempInt1 * tempInt2);
}
else if(inputChar == '/')
{
if(!popInt(iStack, &tempInt2) || !popInt(iStack, &tempInt1))
{
printf("输入的表达式有问题!!!\n");
return 0;
}
pushInt(iStack,tempInt1 / tempInt2);
}
}
这段代码中值得一提的就是对数字的处理,首先,如果我们读到了第一个数字,那么先把它保存在一个变量inputInt中,如果下一次读到的还是一个数字,那么将原来的inputInt变量乘以10然后加上读到的数字,如果读到的不是数字,那么就将inputInt放到栈顶,并将inputInt变量清零。这样我们就把一串字符转化成了一个整型变量了。
这样通过这两步我们就可以计算出一个整型的中缀表达式的计算结果了。其实问题就是怎样管理数与数之间的计算顺序,也就是先括号,后乘除,最后才是加减。或者说就是先乘除后加减,而括号就是用来破坏这一规则的。那么整个过程就是讨论‘+’ ‘-’ ‘*’ ‘/’的运算顺序 ,而后缀表达式可以很清楚地表达这种顺序:从左往右,只要遇到运算符,就把前两个数作为该运算符的操作数,并计算其结果,依次类推,直到计算出最后结果。所以转化成后缀表达式之后可以更方便地计算出表达式的结果。