顺序栈的应用五:表达式求值

表达式求值

这里介绍一种简单直观的“算符优先法”。

要对一个表达式求值,首先要能够正确解释表达式。
例如,要求对下面的算术表达式求值:
4+2*3-10/5

首先要了解算术四则运算的规则。即:
(1)先乘除,后加减
(2)从左算到右
(3)先括号内,后括号外。

由此,这个算术表达式的计算顺序应为:
4 + 2 * 3 - 10 / 5 = 4 + 6 - 10 / 5 = 10 - 10 / 5 = 10 - 2 =8

算符优先法就是根据这个运算关系的规定来实现对表达式的编译或解释执行的。
任何一个表达式都是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。
一般的,操作数既可以是常数也可以是被说明为变量或常量的标识符;
运算符可以分为算术运算符、关系运算符和逻辑运算符3类;
基本界限符有左右括号和表达式结束符等。

我们把运算符和界限符统称为算符,他们构成的集合命名为OP。根据上述3条运算规则,在运算的每一步中,任意两个相继出现的算符a和b之间的优先关系至多是下面三种关系之一:
a<b:a的优先级低于b
a=b:a的优先级等于b
a>b:a的优先级大于b

下表给出了部分a、b运算符之间的优先级关系(列为a运算符,行为b运算符):

+ - * / % ( )
+ > > < < < < >
- > > < < < < >
* > > > > > < >
/ > > > > > < >
% > > > > > < >
( < < < < < < =
) > > > > > N N

表达式中的‘=’表示当左右括号相遇时,括号内的运算完成,此时要把左右括号从表达式中及时脱离。
表达式中的'N'表示这种状态如果出现了,则表达式格式有误,一定是左右括号不匹配。

在代码中,我给出了上述7种运算符

char opetr_char[7]={'+','-','*','/','%','(',')'};

首先是要判断一个字符属不属于运算符,

int isOpetrChar(char ch){
    int i;
    for(i=0;i<7;i++){
        if(ch ==  opetr_char[i]) return i;
    }
    return -1;
}

上表的优先级用一个char型的二维数组直观表示:

char opetr_priority[7][7]={
    {'>','>','<','<','<','<','>'},
    {'>','>','<','<','<','<','>'},
    {'>','>','>','>','>','<','>'},
    {'>','>','>','>','>','<','>'},
    {'>','>','>','>','>','<','>'},
    {'<','<','<','<','<','<','='},
    {'>','>','>','>','>','N','N'}
};

那么,比较两个运算符的优先级可以简单得出:

char getOpetrPrior(char a,char b){
    int i = isOpetrChar(a);
    int j = isOpetrChar(b);
    return opetr_priority[i][j];
}

实现算符优先算法,可以使用两个栈,一个optr_stack,用于存放运算符,一个opnd_stack,用于存放操作数。
算法的核心思想是,依次读入表达式中每个字符,若是操作数则进opnd_stack,若是运算符则与optr_stack的栈顶操作符比较优先权后作相应操作,直至整个表达式求值完毕。

EvaluateExpression.c利用了前面的C封装的顺序栈对象 用线性表表示的顺序栈

实现了输入表达式,显示每一步的计算过程,并输出最终结果的功能。
且支持表达式格式检测,支持多重括号结构、负数运算(‘-’号既可能为减号,也可能为负号)。

github源码

EvaluateExpression.c文件

#include <stdio.h>
#include <malloc.h>
#include "LinearListStack.h"

char opetr_char[7]={'+','-','*','/','%','(',')'};

//operational character priority (a,b),operational character b after a.
//   b: +  -  *  /  %  (  )
// a:
// +    >  >  <  <  <  <  >
// -    >  >  <  <  <  <  >
// *    >  >  >  >  >  <  >
// /    >  >  >  >  >  <  >
// %    >  >  >  >  >  <  >
// (    <  <  <  <  <  <  =
// )    >  >  >  >  >  N  N

char opetr_priority[7][7]={
    {'>','>','<','<','<','<','>'},
    {'>','>','<','<','<','<','>'},
    {'>','>','>','>','>','<','>'},
    {'>','>','>','>','>','<','>'},
    {'>','>','>','>','>','<','>'},
    {'<','<','<','<','<','<','='},
    {'>','>','>','>','>','N','N'}
};

int isOpetrChar(char ch){
    int i;
    for(i=0;i<7;i++){
        if(ch ==  opetr_char[i]) return i;
    }
    return -1;
}

char getOpetrPrior(char a,char b){
    int i = isOpetrChar(a);
    int j = isOpetrChar(b);
    return opetr_priority[i][j];
}

double my_pow(double a,int b){
    int s = 0,i;
    double r = 1;
    if(b == 0) return 1;
    if(b<0){
        b*=-1;
        s = 1;
    }
    for(i = 0; i < b; i++){
        r *= a;
    }
    if(s) r = 1/r;
    return r;
}

int getOpndNumFromStack(LinearListStack *opnd_stack){
    int num = 0,i = 0;
    char elem;
    if(opnd_stack->length(opnd_stack)){
        opnd_stack->pop(opnd_stack,&elem);
        if(elem == ' '){
            while(elem == ' '){
                if(opnd_stack->length(opnd_stack) == 0) break;//确保不会pop空栈
                opnd_stack->pop(opnd_stack,&elem);
            }
        }
        if(elem != ' '){ //两个判断必须分开来写
            while(elem != ' ' && elem != '-'){
                num += my_pow(10,i)*(elem - 0x30);
                if(opnd_stack->length(opnd_stack) == 0) break; //确保不会pop空栈
                opnd_stack->pop(opnd_stack,&elem);
                i++;
            }
            if(elem == '-'){ //如果是负号
                num = -num;
            }else if(elem == ' '){  //将移出的空格间隔符再补进去
                opnd_stack->push(opnd_stack,&elem);
            }
        }
    }
    return num;
}

int operate(int number_a,char opetr_char,int number_b){
    int result = 0;
    switch(opetr_char){
        case '+':
            result = number_a + number_b;
            break;
        case '-':
            result = number_a - number_b;
            break;
        case '*':
            result = number_a * number_b;
            break;
        case '/':
            result = number_a / number_b;
            break;
        case '%':
            result = number_a % number_b;
            break;
        default:
            result = number_b;
            break;
    }
    return result;
}

void pushResultToStack(LinearListStack *opnd_stack, int result){
    char elem[10],n_elem;
    int i = 0,index = 0;
    if(result < 0){
        result = - result;
        n_elem = '-';
        opnd_stack->push(opnd_stack,&n_elem);
    }else if(result == 0){
        n_elem = '0';
        opnd_stack->push(opnd_stack,&n_elem);
    }
    while(result > 0){
        elem[index] = (result % 10) + 0x30;
        result /= 10;
        index++;
    }
    for(i=index;i>0;i--){
        opnd_stack->push(opnd_stack,&elem[i-1]);
    }
}

LinearListStack *evaluateExpression(char *str){
    char elem,opetr_prior,opetr_char;
    int cal_result = 0,number_a,number_b;
    int num_before_flag = 0;
    LinearListStack *optr_stack = InitLinearListStack(); //运算符栈
    LinearListStack *opnd_stack = InitLinearListStack(); //操作数栈
    while(*str != '\0'){
        if(isOpetrChar(*str) != -1){
            if(optr_stack->length(optr_stack)){
                optr_stack->getTop(optr_stack,&elem);
                opetr_prior = getOpetrPrior(elem,*str);
                switch(opetr_prior){
                    case '<': //栈顶运算符优先级低
                        if(num_before_flag == 0){ //前一个入栈的是符号
                            if(*str == '-'){ //此时'-'号表示为负号
                                opnd_stack->push(opnd_stack,str);//'-'号加入操作数栈
                                num_before_flag = 1; //加入的是数字
                            }else if(elem == '(' || *str == '('){ //多个括号与操作符相邻的情况
                                optr_stack->push(optr_stack,str);  
                                elem = ' '; //数字字符加入空格间隔符
                                opnd_stack->push(opnd_stack,&elem);
                                num_before_flag = 0;//加入运算符
                            }else{
                                printf("Expression format error!");
                                break;
                            }
                        }else{ //前面有数字入栈
                            optr_stack->push(optr_stack,str);  
                            elem = ' '; //数字字符加入空格间隔符
                            opnd_stack->push(opnd_stack,&elem);
                            num_before_flag = 0;//加入运算符
                        }
                        break;
                    case '=':
                        optr_stack->pop(optr_stack,&elem);//脱括号
                        num_before_flag = 1; //脱括号等效为加入的是数字
                        break;
                    case '>': //栈顶运算符优先级高
                        if(num_before_flag == 0){ //前一个入栈的是符号
                            if(*str == '-'){ //此时'-'号表示为负号
                                opnd_stack->push(opnd_stack,str);//'-'号加入操作数栈
                                num_before_flag = 1; //加入的是数字
                            }else{
                                printf("Expression format error!");
                                break;
                            }
                        }else{ //前一个入栈的是数字
                            optr_stack->pop(optr_stack,&opetr_char);//取运算符
                            number_b = getOpndNumFromStack(opnd_stack);
                            number_a = getOpndNumFromStack(opnd_stack);
                            cal_result = operate(number_a,opetr_char,number_b);
                            printf("%d",number_a);
                            printf(" %c ",opetr_char);
                            printf("%d = ",number_b);
                            printf("%d\n",cal_result);
                            pushResultToStack(opnd_stack, cal_result);
                            num_before_flag = 1; //加入的是数字
                            str--;
                        }
                        break;
                }
            }else{
                //第一个运算符,此时运算符栈是空的
                if(num_before_flag == 0){ //前面没有数字入栈
                    if(*str == '-'){ //此时'-'号表示为负号
                        opnd_stack->push(opnd_stack,str);//'-'号加入操作数栈
                        num_before_flag = 1; //加入的是数字
                    }else if(*str == '('){
                        optr_stack->push(optr_stack,str);  
                        elem = ' '; //数字字符加入空格间隔符
                        opnd_stack->push(opnd_stack,&elem);
                        num_before_flag = 0;//加入运算符
                    }else{
                        printf("Expression format error!");
                        break;
                    }
                }else{ //前面有数字入栈
                    optr_stack->push(optr_stack,str);  
                    elem = ' '; //数字字符加入空格间隔符
                    opnd_stack->push(opnd_stack,&elem);
                    num_before_flag = 0;//加入运算符
                }
            }
        }else{
            if(*str >= 0x30 && *str <= 0x39){
                opnd_stack->push(opnd_stack,str);
                num_before_flag = 1; //加入的是数字
            }
        }
        str++;
    }
    if(*str == '\0'){ //输入完成
        while(optr_stack->length(optr_stack)){
            optr_stack->pop(optr_stack,&opetr_char);//取运算符
            if(isOpetrChar(opetr_char)<5){
                number_b = getOpndNumFromStack(opnd_stack);
                number_a = getOpndNumFromStack(opnd_stack);
                cal_result = operate(number_a,opetr_char,number_b);
                printf("%d",number_a);
                printf(" %c ",opetr_char);
                printf("%d = ",number_b);
                printf("%d\n",cal_result);
                pushResultToStack(opnd_stack, cal_result);
            }
        }
    }
    DestroyLinearListStack(optr_stack);
    return opnd_stack;
}

int main(void)
{
    int i;
    char string[1000];
    LinearListStack *optr_result = NULL;
    printf("please enter a expression:");
    gets(string);
    optr_result = evaluateExpression(string);
    printf("%s = ",string);
    optr_result->risePrint(optr_result);
    DestroyLinearListStack(optr_result);
    return 0;
}

编译:

gcc LinearListStack.c LinearListStack.h EvaluateExpression.c -o EvaluateExpression

运行EvaluateExpression:

please enter a expression:3+3+(4*5)
3 + 3 = 6
4 * 5 = 20
6 + 20 = 26
3+3+(4*5) = 26

please enter a expression:-2+3+4*5
-2 + 3 = 1
4 * 5 = 20
1 + 20 = 21
-2+3+4*5 = 21

please enter a expression:-2+(3+4)*-5
3 + 4 = 7
7 * -5 = -35
-2 + -35 = -37
-2+(3+4)*-5 = -37

please enter a expression:3+3+((4*5))
3 + 3 = 6
4 * 5 = 20
6 + 20 = 26
3+3+((4*5)) = 26

please enter a expression:-2+(3/4)*-5
3 / 4 = 0
0 * -5 = 0
-2 + 0 = -2
-2+(3/4)*-5 = -2

please enter a expression:-2+(3%4)*-5
3 % 4 = 3
3 * -5 = -15
-2 + -15 = -17
-2+(3%4)*-5 = -17
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,149评论 0 13
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,383评论 0 5
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    许先生__阅读 605评论 0 3
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    徵羽kid阅读 678评论 0 0
  • •1 C语言程序的结构认识 用一个简单的c程序例子,介绍c语言的基本构成、格式、以及良好的书写风格,使读者对c语...
    CONLYOUC阅读 8,705评论 9 66