帶括號表達式求值(數據結構與算法分析課程設計)


帶括號表達式求值

算法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