带括号表达式求值(数据结构与算法分析课程设计)


带括号表达式求值

算法1:双栈法 (2019.11)

算法描述:

(1)规定运算符优先级(详见具体操作步骤)

(2)对输入的字符逐一检验

  (a)如果是数字字符:按位权转化为数值

  (b)如果不是数字字符:将上一步的数值压栈

    i 如果是'('或符号栈为空:将该字符压入符号栈

    ii 如果是')'或'=':将符号栈中所有符号弹出,每弹出一个符号从数据栈拿出两个数字进行计算,计算结果压入数据栈,直到数据栈为空或栈顶元素为'('为止

    iii 其他情况:比较当前元素与栈顶元素的优先级

      (i) 如果当前元素优先级 栈顶元素优先级:将当前元素压入符号栈

      (ii) 如果当前元素优先级 <= 栈顶元素优先级:符号栈弹出一个符号进行运算,直到当前元素优先级 > 栈顶元素优先级

 

关键点提醒:

  使用双栈法时,如何确保表达式是从左往右计算的?这是处理当前元素与栈顶元素优先级相等关系时决定的,如果当前元素优先级小于等于栈顶,则直接进行运算,把运算结果存入数值栈。试想如果等于时不是弹栈进行运算,而是压栈,那么遇到优先级较小的运算符需要弹栈时,则会造成从右向左运算,这种情况下有减法运算时会出错(如3-4+8)

 

具体操作步骤:

规定运算符优先级:

运算符

(

+  -

x  /  %

^

优先级

0

1

2

3

(1)首先,建立数值栈、符号栈两个栈分别存储操作符和操作数。

(2)将数字字符转化成数值。得到经过预处理的字符串后,从头到尾依次读取单个字符。为了将数字字符转化成数值,我们建立了state_sign和state_fraction两个标志位。一开始,将state_sign标志位置1,表示正在读取数字字符(数字字符还没读完),state_fraction标志位置0,表示即将读取的是一个数整数部分。如果遇到数字,并且state_fraction状态是0,则将单个字符的数值(这里指的是当前数字字符的ASCⅡ编码值减去字符‘0’ ASCⅡ编码值,下同)每次乘以10以后相加(最初d=0,0*10=0);遇到小数点‘.’,则将state_fraction状态置为1,表明即将读取的是一个数的小数部分;在此状态下读取数字字符,则依次将单个字符数值除以10后相加。

(3)将符号字符与符号栈顶字符优先级比较后决定是否存入符号栈。如果遇到的不是数字字符,则将state_sign置为0,表明数字字符已经转化成数值,可以将其放入数值栈中。如果读到的是‘(’,或者符号栈为空,则将当前字符入符号栈,如果遇到的是‘)’或者‘=’,则将符号栈全部出栈,(一次弹出两个,进行计算后存入数值栈)直到符号栈为空,或者栈顶元素为‘(’,如果遇到的不是上述符号并且符号栈不为空,则将当前字符与符号栈栈顶字符优先级进行比较(根据程序需要,我们将字符优先级定义为上面“四.1”所示),如果当前字符优先级小于等于栈顶字符(这里包含等于是为了保证从左到右的运算法则),则直接两次取出数值栈栈顶进行运算,并将运算结果存入栈顶;如果当前字符优先级大于栈顶字符,则把当前字符放入符号栈。

(4)对整个字符串执行上述操作,最终栈顶元素就是最后的计算结果。

 

代码实现:(可处理异常;支持负数、小数、多位数运算;支持加减乘数乘方模运算功能)

#include<iostream>
#include<stack>
#include<string.h>
#include<math.h>

using namespace std;
stack<double> data;
stack<char> sign;

int priority(char ch){ //运算符优先级判断 
    if(ch == '+'||ch == '-') return 1;
    if(ch == 'x'||ch == '/'|| ch== '%') return 2;
    if(ch == '^') return 3;
    if(ch == '(') return 0;
}

double aRb(char ch){ //判断运算类型 
    double a,b;
    a = data.top();
    data.pop();
    b = data.top();
    data.pop(); //依次取出栈顶两个元素 
    switch(ch){
        case '+' :
            b += a;
            break;
        case '-' :
            b -= a;
            break;
        case 'x' :
            b *= a;
            break;
        case '/' :
            if(a==0){
                throw "Divider can not be zero!"; //除数为0,抛出异常 
            }else {
                b /= a;
                break;
            }
        case '%' :
            if(a==0){
                throw "divider can not be zero!"; //除数为0,抛出异常
            }else {
                b = (double)((int)b%(int)a);
                break;
            }
        case '^' :
            b = pow(b,a); //乘方、开方运算 
            break;
    }
    return b;
}

int base(char temp){ //判断字符类型 
    char base_digtal[] = {"1234567890"};
    char base_sign[] = {"+-x/%^"};
    char base_others[] = {"().="};
    if(strchr(base_digtal,temp)) return 1;
    else if(strchr(base_sign,temp)) return 2;
    else if(strchr(base_others,temp)) return 3;
    else return 0;
}

void showerror(char temp[],int e){ //指出第一次出现错误的位置 
    for(int i=0;i<strlen(temp);i++){
        if(i==e) cout<<temp[e]<<"<| ";
        else cout<<temp[i];
    }
    cout<<endl;
}
bool precheck(char temp[]){
    stack<char> t;
    for(int i=0;i<strlen(temp);i++){
        if((temp[i]&0x80) && (temp[i+1]&0x80)){//如果字符高位为1且下一字符高位也是1则有中文字符
            cout<<"请不要输入中文字符"<<endl;
            showerror(temp,i+1);
            return false;
        }
        if(temp[i] == '='&& i!=strlen(temp)-1){ //如果等号出现在表达式中间则报错 
            cout<<"Invalid input"<<endl;
            showerror(temp,i);
            return false;
        }
        if(!base(temp[i])){
            cout<<"Please input the sign appointed!"<<endl;//出现未知符号 
            showerror(temp,i);
            return false; 
        }
        if((temp[i]=='('||temp[i]==')')&&i!=0&&i!=strlen(temp)-1&&base(temp[i-1])==1&&base(temp[i+1])==1){
            cout<<"Both sides of parenthesis are numbers!"<<endl; //符号省略 
            showerror(temp,i);
            return false;
        } 
        if(((base(temp[0])==2)&&temp[0]!='-')||(i==strlen(temp)-1&&base(temp[i])==2)||(base(temp[i])==2&&base(temp[i+1])==2)){
            cout<<"Signs redundancy!"<<endl;//符号冗余(重复输入) 
            showerror(temp,i);
            return false;
        }
        if(temp[i]=='.'&&base(temp[i+1])!=1){
            cout<<"The fraction part you put is wrong!"<<endl;//小数部分输入错误 
            showerror(temp,i);
            return false;
        }
        if(temp[i]=='(') t.push(temp[i]);
        if(temp[i]==')') t.pop();    
    }
    if(t.empty()) return true;
    else {
        cout<<"Parenthesis is not matching!"<<endl;//括号不匹配 
        return false;
    }    
}
double calculate(char temp[]){
    double d = 0;
    bool state_fraction = 0;//判断当前数字字符在小数点前面还是后面 
    bool state_sign = 1;//判断一个数是否读完 
    int count = 1;
    for(int i=0;i<strlen(temp);i++){
        //把数字字符处理成数值 
        if(!state_fraction&&base(temp[i])==1){
            d *= 10; 
            d += (double)(temp[i] - '0');
            state_sign=0;
        }else if(temp[i]=='.'){
            state_fraction = 1;
            
        }else if(state_fraction&&(base(temp[i])==1)){
            d += (double)(temp[i] - '0')/pow(10,count);
            count++;
            state_sign=0;
        }else{
            if(!state_sign) {
                data.push(d);
            }
            d = 0;
            state_fraction = 0;
            count = 1;
            if(temp[i]=='('||sign.empty()){//如果是(或栈为空直接入栈 
                sign.push(temp[i]);
            }else if(temp[i]==')'||temp[i]=='='){
            //如果遇到)或者=,则符号栈全部出栈,同时从数值栈取出两个数进行运算 
                while(!sign.empty()&&sign.top()!='('){//根据短路原则,注意顺序 
                    double res = aRb(sign.top());
                    data.push(res);
                    sign.pop();
                }
                if(!sign.empty()) sign.pop();//弹出( 
            }else if(priority(temp[i])<=priority(sign.top())){
            //如果优先级小于等于栈顶,则直接进行运算,把运算结果存入数值栈 
            //等号的位置决定从左到右进行运算  
                while(!sign.empty()&&priority(temp[i])<=priority(sign.top())){
                    double res = aRb(sign.top());
                    data.push(res);
                    sign.pop();
                }        
                sign.push(temp[i]);
            }else if(priority(temp[i])>priority(sign.top())){
            //如果优先级大于栈顶,则入符号栈 
                sign.push(temp[i]);
            }
            state_sign = 1;    //遇到非数字字符表示数字部分已读完 
        }
    }
    return data.top(); //栈顶元素即为最终运算结果 
}

int main() {
    cout<<"---------Welcome!---------"<<endl;
    cout<<"----Input exit to exit----"<<endl; 
    double ans; //最终计算结果 
    while(1){
        char temp[300]; //存储读入的字符串,用于预处理等操作 
        memset(temp,0,300);  
        while(!data.empty()) data.pop();
        while(!sign.empty()) sign.pop(); //初始化
        cin.getline(temp,300);
        if(strcmp(temp,"exit")==0) break; //程序出口 
        //展示更精确的结果
        if(strcmp(temp,"show more")==0){  
            printf("ans = %.9lf\n",ans);
            continue;
        }
        //对空格进行处理
        for(int i=0;i<strlen(temp);i++){
            if(temp[i]==' ')
                for(int k=i;k<strlen(temp);k++) 
                    temp[k] = temp[k+1];
        } 
        if(!precheck(temp)) continue; //预处理结果判断 
        //对'-'前没有数字的情况进行加0处理 
        if(temp[0]=='-'){
            int k = strlen(temp);
            for(k;k>0;k--) temp[k] = temp[k-1];
            temp[0] = '0';
        }
        for(int i=1;i<strlen(temp);i++){
            if(temp[i]=='-'&& base(temp[i-1])!=1&&temp[i-1]!=')'){
                int k = strlen(temp);
                for(k;k>i;k--) temp[k] = temp[k-1];
                    temp[i] = '0';
            }
        }    
        //如果用户没有输入'=',在字符串末尾添加'=' 
        int k = strlen(temp);
        temp[k] = '\0';
        if(temp[k-1]!='=') {
            temp[k] = '=';
            temp[k+1] = '\0';
        }
        //运行异常检测 
        try{
            ans = calculate(temp);
            cout<<"ans = "<<ans<<endl;    
        }catch(const char*message){
            cout<<message<<endl;
        }    
    }
    return 0;
}
//测试数据
//-(-3+(3x(5/2+(3^2%5)+5)x2)/3)+((2x5-3)x2)=
//-(-2+3-5)=
//12+.3-.5x2
//.3+4-2x(3+5/2^3)-.3
//-3.4+4
//3.22.4+2?
//2.3x(3+3.4)-4/2.2
//(2-(-(-(-(-(-2)x3)x1)x.3)/2.3)-2)/2
//2.6125 + 0.04150390625
View Code

 

 

算法2:基于栈的逆波兰算法  (201911)

算法具体步骤:

(1)建立一个操作符栈(临时存放操作符);再建立一个队列(存放后缀表达式);

再建立一个栈(后缀表达式求值时临时存放操作数);

后缀表达式中有数字(double)有字符(char),后缀表达式要存放在一个栈中,可以将队列的基本类型定义为结构体类型

 

(2)将中缀表达式转换成后缀式(逆波兰表达式)

  (a) 从左到右读进中缀表达式的每个字符。

  (b)  如果读到的字符为操作数,则直接输出到后缀表达式中(注意多位数和小数的处理参照算法1(有优化))。

  (c)如果遇到“)”,则弹出栈内的运算符,直到弹出到一个“(”;如果遇到“=”,则弹出栈内所有运算符

  (d) “(”的优先级在栈内比任何运算符都小,但可直接入栈

  (e)当运算符准备进入栈内时,和栈顶的运算符比较,如果外面的运算符优先级高于栈顶的运算符的优先级,则压栈;如果优先级低于或等于栈顶的运算符的优先级,则弹栈到后缀表达式中。直到栈顶的运算符的优先级低于外面的运算符优先级或者栈为空时,再把外面的运算符压栈。

  (f)中缀表达式读完后,如果运算符栈不为空,则将其内的运算符逐一弹出,输出到后缀表达式中。

(2)然后对后缀表达式进行求值

  (a)从头到尾读取表达式,如果遇到数字就压栈。

  (b)如果遇到运算符,弹出两个数进行运算,将运算结果压栈。

 

代码实现:(为了简便起见,没有处理异常;支持负数、小数、多位数运算;支持加减乘数乘方模运算功能)20200202

#include<iostream>
#include<stack>
#include<queue> 
#include<string>
#include<cstring>
#include<cmath>

using namespace std;
typedef struct{
    double num; //操作数 
    char op; //操作符
    bool flag; //true为操作数,false为操作符 
}node;

stack<char> sign; //临时存放操作符 
queue<node> suffix; //存放后缀表达式 
stack<double> data; //后缀表达式求值时临时存放操作数 

int priority(char ch){ //运算符优先级判断 //《算法笔记》中用map来映射运算符优先级 
    if(ch == '+'||ch == '-') return 1;
    if(ch == 'x'||ch == '/'|| ch== '%') return 2;
    if(ch == '^') return 3;
    if(ch == '(') return 0;
}

void aRb(char ch){ //判断运算类型 
    double a,b;
    a = data.top();
    data.pop();
    b = data.top();
    data.pop(); //依次取出栈顶两个元素 
    switch(ch){
        case '+' :
            b += a;
            break;
        case '-' :
            b -= a;
            break;
        case 'x' :
            b *= a;
            break;
        case '/' :
            if(a==0){
                throw "Divider can not be zero!"; //除数为0,抛出异常 
            }else {
                b /= a;
                break;
            }
        case '%' :
            if(a==0){
                throw "divider can not be zero!"; //除数为0,抛出异常
            }else {
                b = (double)((int)b%(int)a);
                break;
            }
        case '^' :
            b = pow(b,a); //乘方、开方运算 
            break;
    }
    data.push(b);
}

int base(char temp){ //判断字符类型 
    char base_digtal[] = {"1234567890"};
    char base_sign[] = {"+-x/%^"};
    char base_others[] = {"().="};
    if(strchr(base_digtal,temp)) return 1;
    else if(strchr(base_sign,temp)) return 2;
    else if(strchr(base_others,temp)) return 3;
    else return 0;
}


void change(string temp){
    for(int i=0;i<temp.length();){ //从左到右依次读中缀表达式
        if(base(temp[i]) == 1 || temp[i] =='.'){
            //把数字字符处理成数值
            double d = 0;
            bool state_fraction = 0;//判断当前数字字符在小数点前面还是后面 
            int count = 1;
            while(base(temp[i]) == 1 || temp[i] =='.'){
                if(!state_fraction&&base(temp[i])==1){
                    d *= 10; 
                    d += (double)(temp[i] - '0');
                }else if(temp[i]=='.'){
                    state_fraction = 1;
                    
                }else if(state_fraction&&(base(temp[i])==1)){
                    d += (double)(temp[i] - '0')/pow(10,count);
                    count++;
                }
                i++;
            }
            node t;
            t.num = d;
            t.flag = 1;
            suffix.push(t); //数值入栈   
        }else{//处理非数字字符
            if((sign.empty()||temp[i] == '(') && temp[i]!='=') sign.push(temp[i]);
            else if(temp[i] == ')'){
                while(!sign.empty() && sign.top()!='('){
                    node t;
                    t.op = sign.top();
                    t.flag = 0;
                    suffix.push(t);
                    sign.pop();    
                }
                if(!sign.empty()) sign.pop(); //pop操作前都必须判断是否为empty 
            } 
            else if(temp[i] == '='){
                while(!sign.empty()){
                    node t;
                    t.op = sign.top();
                    t.flag = 0;
                    suffix.push(t);
                    sign.pop();
                } 
            }
            else if(base(temp[i]) == 2){
                if(priority(temp[i])<=priority(sign.top())){
                    while(!sign.empty()&&priority(temp[i])<=priority(sign.top())){
                        node t;
                        t.op = sign.top();
                        t.flag = 0;
                        suffix.push(t);
                        sign.pop();
                    }    
                }
                sign.push(temp[i]);
            }
            i++;    
        } 
        
    }
}

double calculate(){
    while(!suffix.empty()){
        if(suffix.front().flag == 1) {
            printf("%lf ",suffix.front().num);
            data.push(suffix.front().num);
        }
        else if(suffix.front().flag == 0){
            printf("%c ",suffix.front().op);
            aRb(suffix.front().op);    
        }
        suffix.pop();
    }
    return data.top();
}

int main() {
    double ans; //最终计算结果 
    while(1){
        string temp; //存储读入的字符串,用于预处理等操作   
        while(!data.empty()) data.pop();
        while(!sign.empty()) sign.pop(); //初始化
        getline(cin,temp);
        if(temp == "exit") break; //程序出口 
        string::iterator it = temp.end() - 1;
        if(*it != '=') temp.insert(it+1,'=');//如果用户没有输入'=',在字符串末尾添加'=' 
        for(it = temp.begin();it!=temp.end();it++){
            if(*it == ' ') temp.erase(it); //对空格进行处理;注意先检验空格,再进行下一步处理 
            if(it==temp.begin() && *it=='-') temp.insert(it,'0');
            if(*it == '-'){ 
                if(base(*(it-1))!=1 && *(it-1)!=')')
                    temp.insert(it,'0'); //对'-'前没有数字的情况进行加0处理 
            }
        } 
        cout<<temp<<endl;
        change(temp);
        ans = calculate();
        cout<<endl;
        cout<<"ans = "<<ans<<endl;      
    }
    return 0;
}
//测试数据
//-(-3+(3x(5/2+(3^2%5)+5)x2)/3)+((2x5-3)x2)=
// - ( - 2 + 3 - 5 ) =
//12+.3-.5x2
//.3+4-2x(3+5/2^3)-.3
//-3.4+4
//2.3x(3+3.4)-4/2.2
//(2-(-(-(-(-(-2)x3)x1)x.3)/2.3)-2)/2
View Code

 

 

算法3:基于二叉树的逆波兰算法 (20200122)

算法描述:将中缀表达式用二叉树结构存储。二叉树的后序遍历即为后缀表达式,然后用逆波兰算法求后缀表达式的值。

 

如何构建二叉树?(摘自课件)

1. If the current token is a '(', add a new node as the left child of the current node, and descend to(指向) the left child.

2. If the current token is in the vector ['+','-','/','*'], set the root value of the current node to the operator represented by the current token. Add a new node as the right child of the current node and descend to the right child.

3. If the current token is a number, set the root value of the current node to the number and return to the parent.

4. If the current token is a ')', go to the parent of the current node.

 

关键点提醒:构造二叉树时要注意初始化叶节点的左右孩子为NULL

 

代码实现:(仅支持一位数加减乘除运算,且必须带有括号;也可对字符串进行预处理成下列代码能运行的格式)

#include<iostream>
#include<stack>

using namespace std;
typedef struct BSTNode{
    char value;
    struct BSTNode *parent;
    struct BSTNode *left;
    struct BSTNode *right;
}BSTNode; 
BSTNode *cur = new BSTNode;
int ans;
stack<char> s;

void aRb(char t){
    char temp = s.top();
    s.pop();
    if(t=='+') ans = s.top() + temp;
    else if(t=='-') ans = s.top() - temp;
    else if(t=='x') ans = s.top() * temp;
    else if(t=='/') ans = s.top() / temp;
    s.pop();
    s.push(ans);
}
void InOrder(BSTNode *T){
    if(T!=NULL){
        InOrder(T->left);
        cout<<T->value;
        InOrder(T->right);
    }
}
void PostOrder(BSTNode *T){
    if(T!=NULL){
        PostOrder(T->left);
        PostOrder(T->right);
        cout<<T->value<<" ";
        if(T->value>='0'&&T->value<='9') s.push(T->value - '0');
        else aRb(T->value);
    }
}


int main(){
    cur->parent = NULL; //赋初值很重要 
    char ch = cin.get();
    while(ch != '\n'){
        if(ch=='(') {
            cur->left = new BSTNode;
            cur->left->parent = cur;
            cur = cur->left;
            cur->left = NULL;//赋初值很重要 
            cur->right = NULL;
        }
        else if(ch>='0'&&ch<='9'){
            cur->value = ch;
            cur = cur->parent;
        }
        else if(ch==')'){
            if(cur->parent) cur = cur->parent;
        }
        else if(ch=='+'||'-'||'x'||'/'){
            if(ch=='+') cur->value = '+';
            else if(ch=='-') cur->value = '-';
            else if(ch=='x') cur->value = 'x';
            else if(ch=='/') cur->value = '/';
            cur->right = new BSTNode;
            cur->right->parent = cur;
            cur = cur->right;
            cur->left = NULL;//赋初值很重要 
            cur->right = NULL;
        }
        ch = cin.get();
    }
    cout<<"中缀表达式:";
    InOrder(cur);
    cout<<endl;
    cout<<"后缀表达式:";
    PostOrder(cur);
    cout<<endl;
    cout<<"Answer = "<<ans<<endl;
    return 0;
}
/*----Test----
//注意测试数据需严格按照该标准来定,否则无法构建二叉树 
(3+(4x5))
(3-(4+(5x3)))
(3-(4-(5x(3-(4+5))))
*/
View Code

 

 

反思:

  逆波兰表达式进行一位数运算时,具有很大的优点,但涉及到多位数与小数的运算时,它使用起来需要一点技巧。因为转化为后缀表达式时,需要将数值和运算符存入同一个队列中,而如果是多位数与小数,则必须将数值转化为double型,而不能利用char型(一位数可以利用char型减去'0'的ASCII码),但是运算符是char型,这样就导致了同一个队列中要存入两种类型的元素。

  有两种解决办法:第一种:(思路来源于《算法笔记》P249)将队列的基本类型定义为结构体类型;(如上述代码所示)

  第二种:把运算符double化,即把运算符转为double型类型的数据与数值一道,存入同一个容器中,为了避免运算符的值与数值的值发生冲突,我们必须对数值的范围作出限制(如若我们规定+为99999999,那么所有进行计算的数值必须小于99999999),这样使得运算范围缩小,故不推荐。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM