1. 程式人生 > >【練習】哈工大資料結構實驗——算術表示式求值

【練習】哈工大資料結構實驗——算術表示式求值

一、實驗目的

  1. 通過本實驗,理解棧這種基本資料結構,並掌握程式設計實現棧的一些基本操作函式;
  2. 理解字首、中綴、字尾表示式的定義,學習字首、中綴、字尾表示式的計算;
  3. 用棧的“先進後出”性質進行中綴表示式轉字尾表示式和求值;

二、實驗要求及實驗環境

1、實驗要求:

  1. 從鍵盤任意輸入一個語法正確的中綴表示式, 儲存該表示式;
  2. 利用棧結構,將上述中綴表示式轉換成字尾表示式,並顯示棧的變化過程和所得到的字尾達式。
  3. 利用棧結構,對上式字尾表示式進行求值,並顯示棧的狀態變化過程和最終結果。
  4. 表示式中的字元有:運算子‘+’、‘-’、‘*’、‘/’、‘(’、‘)’;實數;“#”(區分標記負號和減號);

2、實驗環境:

Code:blocks

三、設計思想

1、邏輯設計

1.中綴表示式轉字尾表示式的演算法:
設立一個棧,存放運算子,首先棧為空,程式從左到右掃描中綴表達

  • 若遇到運算元,存放到字尾表示式的陣列中,並輸出一個空格作為兩個運算元的分隔符(這裡注意多位數處理,多位數處理數字與數字之間不要空格);
  • 若遇到運算子,則必須與棧頂比較,運算子級別比棧頂級別高則進否則退出棧頂元素並存放在後綴表示式中,然後輸出一個空格作分隔符;
  • 若遇到左括號,進棧;
  • 若遇到右括號,則一直退棧輸出,直到退到左括號止。當棧變成空時,輸出的結果即為字尾表示式。

2.計算字尾表示式演算法:
設定一個棧,開始時,棧為空,然後從左到右掃描字尾表示式:

  • 若遇運算元,字元型轉化為double型別,進棧;
  • 若遇運算子,則從棧中退出兩個元素,先退出的放到運算子的右邊,後退出的放到運算子左邊,運算後的結果再進棧,直到字尾表示式掃描完畢。
  • 此時,棧中僅有一個元素,即為運算的結果。

2、物理設計

  流程圖(最左邊為主函式,右邊為3個子函式及其呼叫關係):

這裡寫圖片描述

四、測試結果

字首轉字尾:

這裡寫圖片描述

字尾表示式計算:

這裡寫圖片描述


注:-100+((-2.5+3.1*2)/5.1-3.2)/2.8樣例包括了程式所處理的幾種邊界情況:

  • 第一個數為負數處理;
  • 括號後面的第一個數為負數處理;
  • 多位數運算處理;
  • 浮點數計算處理;

五、系統不足與經驗體會

1、系統不足

  1. 定義了兩個棧,但是實際上可以用類模版減少程式碼量;
  2. 不能實現取餘、求階乘等其它一些運算,只能實現加減乘除的四則運算;
  3. 對於輸入非法的中綴表示式沒有進行提示和處理;

2、經驗體會

  1. 在處理多位數的時候用空格間隔來標記不同的數,這樣實現起來比較方便;
  2. 在處理負號的時候,曾嘗試直接把它壓棧當作普通的運算子處理,但會導致後面實現起來比較混亂,後來用特殊字元‘#’來標記負號就容易實現;
  3. 在進行下一次操作之前,要把上一次的陣列清空,這在除錯的時候很隱蔽;
  4. 棧真的很有用,用得好和巧妙能把某些問題解決得很漂亮。

六、原始碼(C++)

#include <iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define  maxlen 100
using namespace std;
struct STACK_CHAR
{
    int top_char;
    char element[maxlen];
} op_s;
struct STACK_NUM
{
    int top_num;
    double element[maxlen];
} num_s;
char mid[100],end[100];//存放中綴和字尾
void makenull_char(STACK_CHAR &S)
{
    S.top_char=maxlen;
}
void makenull_num(STACK_NUM &S)
{
    S.top_num=maxlen;
}
bool empty_char(STACK_CHAR S)
{
    if (S.top_char>maxlen-1)   return true;
    else return false ;
}
bool empty_num(STACK_NUM S)
{
    if (S.top_num>maxlen-1)   return true;
    else return false ;
}
char top_char(STACK_CHAR S)
{
    if (empty_char(S)) return NULL;
    else return(S.element[S.top_char]);
}
double top_num(STACK_NUM S)
{
    if (empty_num(S)) return NULL;
    else return(S.element[S.top_num]);
}
void pop_char(STACK_CHAR &S)
{
    if (empty_char(S))  cout << "STACK_CHAR is empty" << endl;
    else
    {
        S.top_char =S.top_char+1;
        cout << "符號棧中元素:" ;
        for(int i=maxlen-1; i>=S.top_char; i--)  cout << S.element[i] << " ";
        cout << endl;
    }
}
void pop_num(STACK_NUM &S)
{
    if (empty_num(S))  cout << "STACK_NUM is empty" << endl;
    else
    {
        S.top_num =S.top_num+1;
        cout << "數字棧中元素:";
        for(int i=maxlen-1; i>=S.top_num; i--)   cout << S.element[i] << " ";
        cout << endl;
    }
}
void push(char x,STACK_CHAR &S)
{
    if (S.top_char==0)   cout << " STACK_CHAR  is full " << endl;
    else
    {
        S.top_char=S.top_char-1;
        S.element[S.top_char]=x;
        cout << "符號棧中元素:" ;
        for(int i=maxlen-1; i>=S.top_char; i--)  cout << S.element[i] <<  " ";
        cout << endl;
    }
}
void push_num(double x,STACK_NUM &S)
{
    if (S.top_num==0)   cout << " STACK_NUM is full " << endl;
    else
    {
        S.top_num=S.top_num-1;
        S.element[S.top_num]=x;
        cout << "數字棧中元素:";
        for(int i=maxlen-1; i>=S.top_num; i--)    cout << S.element[i] << " ";
        cout << endl;
    }
}
/*棧的一系列基本操作函式*/
int pre_judge(char x);//優先順序判斷
void mid_to_end(char a[],int n);//轉化的函式
double calculate_end(char end[],int n);//計算字尾表示式函式
int main()
{
    int j;
    makenull_char(op_s);
    cout << "請輸入字首表示式:";
    gets(mid);
    cout << "字首表示式為:";
    puts(mid);
    mid_to_end(mid,100);
    cout << endl << "字尾表示式為:" ;
    for (j=0; end[j]!='\0'; j++)
    {
        if(end[j]=='#') cout << '-';//掃描遇到井號那麼顯示負號
        else  cout << end[j];    //顯示字尾表示式
    }
    cout << endl << endl;
    cout <<"表示式的計算結果為:"<< calculate_end(end,100) << endl;
    return 0;
}
int pre_judge(char x)//符號優先順序判斷
{
    int flag=0;
    switch(x)
    {
    case '(':
        flag=1;
        break;
    case ')':
        flag=1;
        break;
    case '+':
        flag=2;
        break;
    case '-':
        flag=2;
        break;
    case '*':
        flag=3;
        break;
    case '/':
        flag=3;
        break;
    default :
        break;
    }
    return flag;
}
void mid_to_end(char a[],int n)//中綴轉字尾函式
{
    int end_count=0,i,m=0;
    if(a[0]=='-')//中綴表示式中是第一個為負號
    {
        end[end_count]='#';//用#號標記負號
        end_count++;
        m=1;//如果為負號,進入迴圈掃描的時候就從第二個字元開始
    }
    for (i=m; a[i]!='\0'; i++)
    {
        if (a[i]<='9'&&a[i]>='0'&&(a[i+1]>'9'||a[i+1]<'0')&&a[i+1]!='.')
        {
            end[end_count]=a[i];
            end_count++;
            end[end_count]=' ';
            end_count++;
        }//當前字元為0~9而且下一個為運算子(即為數字的最後一位,那麼設空格與下一個數字分開)
        else if((a[i]<='9'&&a[i]>='0'&&((a[i+1]<='9'&&a[i+1]>='0')||a[i+1]=='.'))||a[i]=='.')
        {
            end[end_count]=a[i];
            end_count++;
        }//是數字而且不是數字的最後一位,後最中不設空格
        else if(a[i-1]=='('&&a[i]=='-')
        {
            end[end_count]='#';
            end_count++;
        }//中綴表示式掃描到的數字的前一個是左括號則當負號處理
        else if((a[i]=='+'||a[i]=='-'||a[i]=='*'||a[i]=='/')&&a[i-1]!='(')
        {
            while(!empty_char(op_s))
            {
                if(pre_judge(top_char(op_s))>=pre_judge(a[i]))
                {
                    end[end_count]=top_char(op_s);
                    pop_char(op_s);
                    end_count++;
                    end[end_count]=' ';
                    end_count ++;
                }
                else break;
            }
            push(a[i],op_s);
        }/*若遇到運算子,則必須與棧頂比較,運算子級別比棧頂級別高則進棧,
        否則退出棧頂元素並存放在後綴表示式中,然後輸出一個空格作分隔符;*/
        else if (a[i]=='(') push(a[i],op_s);//若遇到左括號,進棧;
        else  if(a[i]==')')
        {
            while(1)
            {
                if(top_char(op_s)=='(')
                {
                    pop_char(op_s);
                    break;
                }
                else
                {
                    end[end_count]=top_char(op_s);
                    pop_char(op_s);
                    end_count++;
                    end[end_count]=' ';//注意設定空格
                    end_count++;
                }
            }
        }//若遇到右括號,則一直退棧輸出,直到退到左括號止。
        else  continue;
    }
    while(!empty_char(op_s))//將棧中剩下的元素彈出
    {
        end[end_count]=top_char(op_s);
        pop_char(op_s);
        end_count++;
        end[end_count]=' ';
        end_count++;
    }
    end[end_count]='\0';//設定字串結束標識
}
double calculate_end(char a[],int n)        //計算字尾表示式的值
{
    double top,sec,ans=0,number;
    int i=0,j=0,k,x;
    bool flag=true;//flag用於標記是否為負數,開始時設定為不是負數
    char num[100];
    STACK_NUM num_s;
    makenull_num(num_s);
    for(i=0; end[i]!='\0'; i++)
    {
        if(end[i]=='#')
        {
            flag=false;
            j=i+1;
        }
        if(end[i]==' '&&end[i-1]>='0'&&end[i-1]<='9')
        {
            for(k=j,x=0; k<=i; k++,x++) num[x]=end[k];
            //在擷取完整的數存放到num中,但是這時的數還是char的,下面用atof函式進行轉化
            number=atof(num);//將char型的數轉化為double
            if(flag==false)
            {
                number=(-1)*number;
                flag=true;//處理完為負數之後那麼把標識又設為不是負數,下一次迴圈使用
            }
            push_num(number,num_s);
            memset(num,' ',sizeof(num));
            number=0;
        }//遇到的是運算元,進棧;
        else if(end[i-1]==' '&&end[i]<='9'&&end[i]>='0')  j=i;
        //掃描到數字的第一個啦那麼把j設定成i一遍迴圈時候num中擷取字串
        else if(a[i]=='+'||a[i]=='-'||a[i]=='*'||a[i]=='/')
        {
            top=top_num(num_s);
            pop_num(num_s);
            sec=top_num(num_s);
            switch (a[i])  /*對不同運算子進行分別處理*/
            {
            case'+':
                ans=sec+top;
                break;
            case'-':
                ans=sec-top;
                break;
            case'*':
                ans=sec*top;
                break;
            case'/':
                ans=sec/top;
                break;
            }
            pop_num(num_s);
            push_num(ans,num_s);
        }
           /*若遇運算子,則從棧中退出兩個元素,
            先退出的放到運算子的右邊,後退出的放到運算子左邊,
            運算後的結果再進棧*/
        else continue;
    }
    return  top_num(num_s);//棧中只有一個元素啦,返回為結果
}