栈的应用十分广泛,例如之前提到的浏览器、图像或文本编辑器等等的回退操作,操作系统使用栈进行内存管理,栈还有一个很重要的应用就是在程序设计语言中实现了递归。
1、进栈转换
十进制与其他进制之间的转换,通常采用除基取余法,余数需要逆序取。栈是一种后进先出的数据结构,当需要逆序操作时,我们就可以使用栈实现十进制与其他进制之间的转换,因为无法限制十进制数的大小,所以这里使用链栈。
这里代码只是实现了十进制与十进制以下进制之间的转换,十进制以上的进制因为结果含有字符,结果转换比较麻烦,非本文主要意义就不再实现。
注:代码仅提供思路。
double Digit(int number) //计算位数
{
double digit = 1;
for (int i=0; i<number; i++)
digit = digit*10;
return digit;
}
double DecimalConversion(int decimalNumber, int other)
{
int modNumber = 0;//入栈或出栈的数值
double result = 0;//最终结果
LinkStackTop *s = InitStack();//初始化一个链栈
while (decimalNumber)//除基直到商为0
{
modNumber = decimalNumber%other;//取余
decimalNumber = decimalNumber/other;//除基
Push(s, modNumber);//余数入栈
}
while (StackLength(s))
{
modNumber = Pop(s);//余数出栈,实现余数逆取
result = result+modNumber*Digit(StackLength(s));//每一个余数乘自己的位数加到结果上
}
DestroyStack(s);//销毁链栈
return result;
}
2、四则运算表达式求值
栈还有一个比较常见的应用:数学四则运算表达式求值。
数学四则运算的规则:先乘除,后加减,从左到右,有括号先括号。
对于计算机来说,理解数学四则运算的规则,并直接计算求值是十分困难的。这里面的困难就在:如果乘除在加减后面,但是乘除却要先运算,而加入括号后就使得运算更加复杂。
因此有一种不需要括号的后缀表达式,是数学四则运算表达式的一种新的显示方法,非常巧妙地解决了程序实现四则运算的困难。
我们通常写出来的数学表达式称为中缀表达式:9+(3-1)*3+8/2,因为所有运算符位于需要运算的两个数字的中间。
而后缀表达式就是所有运算符号位于需要运算的两个数字后面:9 3 1 - 3 * + 8 2 / +(对应上面的中缀表达式)。(3-1)*9/2+8
那么后缀表达式是如何解决中缀表达式的运算困难的呢?
后缀表达式的运算规则:
- 从左到右遍历整个表达式;
- 遇到数字就将数字入栈;
- 遇到运算符就将栈顶的两个数字出栈进行运算,然后将运算结果入栈;
- 一直到遍历结束,栈只有一个数字那就是最终结果;
那中缀表达式如何转换为后缀表达式呢?
中缀表达式转换为后缀表达式的规则:
- 从左到右遍历整个中缀表达式,
- 遇到数字就输出,即变为后缀表达式的一部分;
- 遇到运算符,如果是左括号就入栈;
- 如果是右括号就将左括号之前的元素全部出栈,变为后缀表达式的一部分(左括号也需要出栈,但是后缀表达式中无括号,所以括号不需要变成后缀表达式的一部分);
- 如果不是括号,判断其与栈顶符号的优先级,如果该运算符优先级低于栈顶符号的优先级,就将栈内元素全部出栈,变为后缀表达式的一部分,否则该运算符入栈。
完成四则运算表达式求值,我们需要先将输入的中缀表达式先转换为后缀表达式,再使用后缀表达式进行求值。整个过程,我们需要一直使用到栈,充分利用了栈的先进后出的特性来处理问题,理解好了整个过程也就理解了栈。这里使用数组栈或者链栈都可以,但我一直认为链栈是最好的。
注:代码仅提供思路。
/*中缀表达式转换后缀表达式*/
void ExpressionConversion(char PostfixExpression[], int length)
{
//PostfixExpression是后缀表达式,length为后缀表达式的长度,这两个即是参数也是返回值
char expression;//用于挨个读入中缀表达式的字符
char pop; //用于记录栈每次出栈的元素
char top; //用于记录当前栈顶元素
LinkStackTop *s = InitStack();//初始化链栈
cout<<"输入中缀表达式:";
//因为使用字符挨个读入表达式,所以不识别9以上的数字
while (scanf("%c", &expression) != EOF)
{
if (expression == '\n') break;
if (expression >= '0' && expression <= '9') //遇到数字
PostfixExpression[length++] = expression; //输出数字为后缀表达式
else //遇到运算符
{
if (expression == '(') //遇到左括号入栈
Push(s, expression);
else if (expression == ')')//遇到右括号,右括号不做操作
{
while (1) //左括号之前的元素全部出栈为后缀表达式
{ //左括号也要出栈,但不为后缀表达式
pop = Pop(s);
if (pop == '(') break;
PostfixExpression[length++] = pop;
}
}
else //遇到加减乘除
{
if (StackEmpty(s)==1) //栈不为空,需要比较优先级
{
top = GetTop(s);//获取栈顶元素用于比较优先级
//只有当栈顶元素为乘除,并且新运算符为加减时,新运算符优先级低于栈顶元素
if ((top == '*' || top == '/') && (expression == '+' || expression == '-'))
{
//先将栈内原有元素全部出栈
while (StackEmpty(s)!=0)
PostfixExpression[length++] = Pop(s);
Push(s, expression);//然后新运算符入栈
}
else //其他情况,新运算符都入栈
Push(s, expression);
}
else //栈为空,直接入栈
Push(s, expression);
}
}
}
//此时中缀表达式遍历完毕,就将栈内元素全部出栈
while (StackEmpty(s)!=0)
PostfixExpression[length++] = Pop(s);
PostfixExpression[length++] = '\0';//后缀表达式此时转换完毕
cout<<"转换为后缀表达式:"<<PostfixExpression<<endl;
DestroyStack(s);//用完就销毁链栈
}
/*后缀表达式求值*/
int ExpressionEvaluation()
{
char PostfixExpression[100];//用于保存后缀表达式
int length = 0;//后缀表达式的长度
int result = 0;//最终结果
LinkStackTop *s = InitStack();//初始化链栈
/*中缀表达式转换为后缀表达式*/
ExpressionConversion(PostfixExpression, length);
cout<<"转换为后缀表达式:"<<PostfixExpression<<endl;
for (int i=0; i<length; i++)
{
char expression = PostfixExpression[i];//用于挨个读入后缀表达式的字符
if (expression >= '0' && expression <= '9') //遇到数字就入栈
Push(s, (expression-'0'));
else//遇到运算符
{ //将栈顶的两个元素出栈,并进行运算,然后将结果入栈
int top_1 = Pop(s);
int top_2 = Pop(s);
if (expression == '+')
result = top_2 + top_1;
else if (expression == '-')
result = top_2 - top_1;
else if (expression == '*')
result = top_2 * top_1;
else if (expression == '/')
result = top_2 / top_1;
Push(s, result);
}
}
result = Pop(s);//保留最后结果
DestroyStack(s);//用完就销毁链栈
return result;
}