欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

栈的应用--简单计算器转换知识

程序员文章站 2024-03-19 15:10:58
...
                                栈的应用

1. 表达式求值

  • 1.问题描述:
    这里限定的表达式求值问题是:用户输入一个包含“+”、“-”、“*”、“/”、正整数和圆括号的合法数学表达式,计算该表达式的运算结果。
  • 2.数据组织 
    算术表达式exp采用字符数组表示,其中只含有“+”、“-”、“*”、“/”、正整数和圆括号。
      为了方便,假设该表达式都是合法的数学表达式,例如,exp=”1+2*(4+12)”;在设计相关算法中用到栈,这里采用顺序栈存储结构。
  • 3.设计运算算法
    了解一下算术表达式:

这里所谓的前缀,中缀,后缀是根据操作符的位置来定的,如果操作符在操作数前面,则称为前缀表达式,例如“- + 1 × + 2 3 4 5”;如果操作符在操作数之间,则称为中缀表达式,例如
“1+((2+3)×4)-5”;如果操作符在操作数后面,则称为后缀表达式,例如“1 2 3 + 4 × + 5 -”。

(一):讲一下将中缀表达式转化为后缀表达式
①.虽然中缀表达式符合人类的日常思维习惯,但是计算机在存储中缀表达式时,需要使用树这种数据结构,如果表达式过于复杂,那么树的高度会变得很高,大大增加了时间复杂度和空间复杂度。如果转换成线性结构,那么效率将变得高很多,所以需要将中缀表达式先转换成前缀或者后缀表达式,然后依靠栈这种线性数据结构来进行计算。
②.前缀表达式又叫波兰表达式,后缀表达式又叫逆波兰表达式。前缀表达式基本没有在商业计算机中使用过,所以现实中用的更多的是后缀表达式。
③. 如何将中缀表达式转化成后缀表达式呢?

  • 利用两个栈S1,S2:其中S1存放操作符,S2存放操作数

1.从左往右遍历中缀表达式,如果遇到数字,则放入S2中,如果遇到操作符,则放入S1中。
2.在放操作符的时候有一定的规则,如果栈为空或栈顶元素为”(“,则直接压栈。如果是(,也直接压栈;
3.如果栈顶元素为普通操作符,则比较优先级,如果待压栈的操作符比栈顶操作符优先级高,则直接压栈,否则(如果待压栈的操作符比栈顶操作符优先级低或则相等)将S1中的栈顶元素出栈,并压入S2中,再接着往下比较S1栈顶元素的优先级。
4.如果遇到”)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号”(“为止,此时将这一对括号丢弃。
5.最后将S1中剩余的运算符依次弹出并压入S2,逆序输出S2(从栈底到栈顶)便得到了后缀表达式。(注意:等号的优先级最低,因为要到最后才进行赋值操作)

④.得到后缀表达式之后,计算就变得方便多了,遇到数字就压栈,遇到操作符的时候,pop出栈顶的两个元素,进行计算后将结果又压入栈中,这样一直下去,直到得到最终结果。
将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下:
栈的应用--简单计算器转换知识
因此结果为“1 2 3 + 4 × + 5 -”(需要逆序输出)

(二)再讲一下将中缀表达式转化为前缀表达式:
中缀表达式转换前缀表达式其实过程和转换后缀表达式及其相似,只是对其中的几个不同之处稍微说明一下:

1 转换后缀表达式由于符号是要在操作数后面出现,所以操作数入栈,扫描顺序是从左向右,转换前缀表达式的话操作符需要在操作数前面出现,那么扫描顺序就应该是从右向左。
2 操作符的具体处理方式和转换到后缀表达式略有不同,括号的操作也是’)’直接入栈,’)’则配对’(’后一起出栈
3.在输出的时候,直接按照正常的栈输出即可,而后缀表达式则需逆序输出。
利用两个栈S1,S2:其中S1存放操作符,S2存放操作数

扫描到的元素 S2 栈底->栈顶 S1 栈底->栈顶
5 5
- 5 -
5 -,)
4 5,4 -,)
* 5,4 -,),*
5,4 -,),*,)
3 5,4,3 -,),*,)
+ 5,4,3 -,),*,),+
2 5,4,3,2 -,),*,),+
5,4,3,2,+ -,),*
5,4,3,2,+,* -
+ 5,4,3,2,+,*,- +
1 5,4,3,2,+,*,-,1 +
最右端 5,4,3,2,+,*,-,1,+

前缀,中缀,后缀表达式与二叉树的表示
这三个表达式其实与二叉树是有这很紧密的联系的,a+b*c-(d+e)这个中缀表达式我们将其操作符当做内节点,操作数当做叶子节点,这样的话就能够画出这个中缀表达式所对应的二叉树
栈的应用--简单计算器转换知识

例: 对于表达式“(56-20)/(4+2)”,将其转换成后缀表达式

  • 转换成后缀表达式的过程 如下:
    栈的应用--简单计算器转换知识
    栈的应用--简单计算器转换知识
    栈的应用--简单计算器转换知识
  • 伪代码:
初始化运算符栈op;
将'='进栈;
从exp读取字符ch;
while (ch!='\0')
{   if (ch不为运算符)
   将后续的所有数字均依次存放到postexp中,并以字符'#'标志数值串结束;
     else 
         switch(Precede(op栈顶运算符,ch))
         {
         case '<':   //栈顶运算符优先级低
      将ch进栈;  从exp读取下字符ch;  break;
         case '=':   //只有栈顶运算符为'(',ch为')'的情况
     退栈; 从exp读取下字符ch; break;
         case '>':    //栈顶运算符应先执行,所以出栈并存放到postexp中
     退栈运算符并将其存放到postexp中; break;
        }
}
若字符串exp扫描完毕,则将运算符栈op中'='之前的所有运算符依次出栈并存放到postexp中。最后得到后缀表达式postexp; 
  • 代码实现:
void trans(char *exp,char postexp[])  /*将算术表达式exp转换成后缀表达式postexp*/
{
    char e;
    SqStack *Optr;  /*定义运算符指针*/
    InitStack(Optr);  /*初始化运算符栈*/
    int i=0;  /*i作为postexp的下标*/
    while(*exp!='\0')  /*exp表达式未扫描完时循环*/
    {
        switch(*exp)
        {
        case '(':   /*判定为左括号*/
            Push(Optr,'(');  /*左括号进栈*/
            exp++;  /*继续扫描其他字符*/
            break;
        case ')':  /*判定为又括号*/
            Pop(Optr,e);  /*出栈元素*/
            while(e!='(')  /*不为'('时循环*/
            {
                postexp[i++]=e;  /*将e存放到postexp中*/
                Pop(Optr,e);  /*继续出栈元素*/
            }
            exp++;  /*继续扫描其他字符*/
            break;
        case '+':  /*判定为加或减号*/
        case '-':
            while(!StackEmpty(Optr))  /*栈不空循环*/
            {
                GetTop(Optr,e);  /*取栈顶元素e*/
                if(e!='(')   /*e不是'('*/
                {
                    postexp[i++]=e;  /*将存放到postexp中*/
                    Pop(Optr,e);   /*出栈元素e*/
                }
                else
                    break;
            }
            Push(Optr,*exp);  /*将‘+’或‘-’进栈*/
            exp++;  /*继续扫描其他字符*/
            break;
        case '*':  /*判定为'*'或'/'号*/
        case '/':
            while(!StackEmpty(Optr))  /*栈不空循环*/
            {
                GetTop(Optr);  /*取栈顶元素e*/
                if(e=='*'||e=='/')  /*将栈顶'*'或'/'运算符出栈并存放到postexp中*/
                {
                    postexp[i++]=e;  /*将e存放到postexp中*/
                    Pop(Optr,e);  /*出栈元素*/
                }
                else  /*e为非‘*’或‘/’运算符时退出循环*/
                    break;
            }
            Push(Optr,*exp);  /*将'*'或'/'进栈*/
            exp++;  /*继续扫描其他字符*/
            break;
        default:
            while(*exp>='0'&&*exp<='9')  /*判定为数字字符*/
            {
                postexp[i++]=*exp;
                exp++;
            }
            postexp[i++]='#';  /*用#标示一个数字串结束*/
        }
    }
    while(!StackEmpty(Optr))  /*此时exp扫描完毕,栈不为空时循环*/
    {
        Pop(Optr,e);  /*出栈元素e*/
        postexp[i++]=e;  /*将e存放到postexp中*/
    }
    postexp[i]='\0';  /*给postexp表达式加结束表示*/
    DestroyStack(Optr);  /*销毁栈*/
}

  • 后缀表达式求值。在后缀表达式求值算法中要用到一个数值栈st,该算法实现过程如下:
      对后缀表达式求值过程是:从左到右读入后缀表达式,若读入的是一个操作数,就将它入数值栈,若读入的是一个运算符op,就从数值栈中连续出栈两个元素(两个操作数),假设为x和y,计算x op y之值,并将计算结果入数值栈;对整个后缀表达式读入结束时,栈顶元素就是计算结果。

  • 对后缀表达式postexp求值伪代码

while (从postexp读取字符ch,ch!='\0')
{    若ch为数字,将后续的所有数字构成一个整数存放到数值栈st中。
     若ch为“+”,则从数值栈st中退栈两个运算数,相加后进栈st中。
     若ch为“-”,则从数值栈st中退栈两个运算数,相减后进栈st中。
     若ch为“*”,则从数值栈st中退栈两个运算数,相乘后进栈st中。
     若ch为“/”,则从数值栈st中退栈两个运算数,相除后进栈st中(若除数为零,则提示相应的错误信息)。
}
若字符串postexp扫描完毕,则数值栈op中的栈顶元素就是表达式的值。
  • 对于后缀表达式“56#20#-4#2#+/”的求值过程如下:
    栈的应用--简单计算器转换知识

  • 将算术表达式exp转换成后缀表达式postexp:

代码:

double compvalue(char *postexp)  /*计算后缀表达式的值*/
{
   double d,a,b,c,e;
   SqStack1 *Opnd;  /*定义操作数栈*/
   InitStack1(Opnd);  /*初始化操作数栈*/
   while(*postexp!='\0')  /*postexp字符串未扫描完时循环*/
   {
       switch(*postexp)
       {
       case '+':   /*判定为'+'号*/
            Pop1(Opnd,a);  /*出栈元素a*/
            Pop1(Opnd,b);  /*出栈元素b*/
            c=b+a;  /*计算c*/
            Push1(Opnd,c);  /*将计算结果c进栈*/
            break;
       case '-':  /*判定为'-'号*/
            Pop1(Opnd,a);  /*出栈元素a*/
            Pop1(Opnd,b);  /*出栈元素b*/
            c=b-a;  /*计算c*/
            Push1(Opnd,c);  /*将计算结果c进栈*/
            break;
        case '*':  /*判定为'*'号*/
            Pop1(Opnd,a);  /*出栈元素a*/
            Pop1(Opnd,b);  /*出栈元素b*/
            c=b*a;  /*计算c*/
            Push1(Opnd,c);  /*将计算结果c进栈*/
            break;
        case '/':  /*判定为'/'号*/
            Pop1(Opnd,a);  /*出栈元素a*/
            Pop1(Opnd,b);  /*出栈元素b*/
            if(a!=0)
            {
                c=b/a;  /*计算c*/
                Push1(Opnd,c);  /*将计算结果c进栈*/
                break;
            }
            else
            {
                printf("\n\t除零错误!\n");
                exit(0);  /*异常退出*/
            }
            break;
        default:  /*处理数字字符*/
            d=0;  /*将连续的数字字符转换成对应的数值存放到d中*/
            while(*postexp>='0'&&*postexp<='9')  /*判定为数字字符*/
            {
                d=10*d+*postexp-'0';
                postexp++;
            }
            Push1(Opnd,d);  /*将数值d进栈*/
            break;
       }
       postexp++;  /*继续处理其他字符*/
   }
   GetTop1(Opnd,e);  /*取栈顶元素e*/
   DestroyStack1(Opnd);  /*销毁栈*/
   return e;  /*返回e*/
}
  • main()主函数代码:
int main()
{
    char exp[100];
    char postexp[MaxSize];
    printf("请输入你想要计算的公式:!!\n");
    gets(exp);
    trans(exp,postexp);  /*将exp转换为postexp*/
    printf("中缀表达式:%s\n",exp);  /*输出postexp*/
    printf("后缀表达式:%s\n",postexp);  /*输出postexp的值并输出*/
    printf("表达式的值:%g\n",compvalue(postexp));  /*求postexp的值并输出*/
    return 1;
}