表达式求值(最详细分析+代码实现+表达式之间的相互转换)
目錄
一、概念
二、前綴表達(dá)式的邏輯和實(shí)現(xiàn)方式
1.定義
2.前綴表達(dá)式的計(jì)算機(jī)求值
3.例子
4.代碼實(shí)現(xiàn)
三、中綴表達(dá)式的邏輯和實(shí)現(xiàn)方式
1.定義
2.中綴表達(dá)式規(guī)則
3.中綴表達(dá)式的計(jì)算機(jī)求值
4.代碼實(shí)現(xiàn)
四、后綴表達(dá)式的邏輯和實(shí)現(xiàn)方式(逆波蘭表達(dá)式求值)
1.定義
2.后綴表達(dá)式計(jì)算機(jī)求值
3.例子
4.代碼實(shí)現(xiàn)
五、相互轉(zhuǎn)換
1.中綴表達(dá)式轉(zhuǎn)化為前綴表達(dá)式
①算法描述
②例子
2.前綴表達(dá)式轉(zhuǎn)化為中綴表達(dá)式
3.中綴表達(dá)式轉(zhuǎn)化為后綴表達(dá)式
①算法描述
②例子
③代碼實(shí)現(xiàn)
4.后綴表達(dá)式轉(zhuǎn)化為中綴表達(dá)式
六、總結(jié)
1.常用表達(dá)式求值分析
①方法
②優(yōu)缺點(diǎn)
2.相互轉(zhuǎn)換分析
3.總結(jié)
?
一、概念
算術(shù)表達(dá)式是由操作數(shù)(運(yùn)算數(shù))、運(yùn)算符(操作符)、和界線符(括號)三部分組成,在計(jì)算機(jī)中進(jìn)行算術(shù)表達(dá)式的計(jì)算是通過堆棧來實(shí)現(xiàn)的。
二、前綴表達(dá)式的邏輯和實(shí)現(xiàn)方式
1.定義
如果是在兩個操作數(shù)之前,那么這個表達(dá)式就是前綴表達(dá)式,又稱波蘭表達(dá)式,如:-*+3 5 7 1
2.前綴表達(dá)式的計(jì)算機(jī)求值
1.從右至左掃描表達(dá)式,遇到數(shù)字時(shí),將數(shù)字壓入堆棧,遇到運(yùn)算符時(shí),
2.彈出棧頂?shù)膬蓚€數(shù),用運(yùn)算符對它們做相應(yīng)的計(jì)算(棧頂元素 op 次頂元素),
3.并將結(jié)果入棧;重復(fù)上述過程直到表達(dá)式最左端,最后運(yùn)算得出的值即為表達(dá)式的結(jié)果
3.例子
計(jì)算前綴表達(dá)式的值:- + 1 × + 2 3 4 5
1)從右至左掃描,將5,4,3,2壓入堆棧;
2)遇到+運(yùn)算符,彈出2和3(2為棧頂元素,3為次頂元素),計(jì)算2+3的值,得到5,將5壓入棧;
3)遇到×運(yùn)算符,彈出5和4,計(jì)算5×4的值,得到20,將20壓入棧;
4)遇到1,將1壓入棧;
5)遇到+運(yùn)算符,彈出1和20,計(jì)算1+20的值,得到21,將21壓入棧;
6)遇到-運(yùn)算符,彈出21和5,計(jì)算5-21的值,得到-16為最終結(jié)果
可以看到,用計(jì)算機(jī)計(jì)算前綴表達(dá)式是非常容易的,不像計(jì)算后綴表達(dá)式需要使用正則匹配
4.代碼實(shí)現(xiàn)
?
三、中綴表達(dá)式的邏輯和實(shí)現(xiàn)方式
1.定義
如果是跟在兩個操作數(shù)之間,那么這個表達(dá)式就是中綴表達(dá)式,如:(3 + 5) * 7 - 1
2.中綴表達(dá)式規(guī)則
(1)?先計(jì)算括號內(nèi),后計(jì)算括號外;
(2)?在無括號或同層括號內(nèi),先乘除運(yùn)算,后加減運(yùn)算,即乘除運(yùn)算的優(yōu)先級高于加減運(yùn)算的優(yōu)先級;
(3)?同一優(yōu)先級運(yùn)算,從左向右依次進(jìn)行。
3.中綴表達(dá)式的計(jì)算機(jī)求值
4.代碼實(shí)現(xiàn)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <malloc.h> #include <math.h> #define maximum 100000typedef struct//數(shù)字棧 {float data[maximum];int top; }number;typedef struct//字符棧 {char data[maximum];int top; }sign;void InitNumber(number *stack);//初始化數(shù)字棧 void GetTopNumber(number stack, float *e);//獲取棧頂元素 void PushNumber(number *stack, float e);//進(jìn)棧 void PopNumber(number *stack, float *e);//出棧 void InitSign(sign *stack); void GetTopSign(sign stack, char *e); void PushSign(sign *stack, char e); void PopSign(sign *stack, char *e);void Calculate(number *stack, char e);number Num; sign sig; char expression[maximum];int main() {gets(expression);int length;length=strlen(expression);int i;float en,n;char es;InitNumber(&Num);InitSign(&sig);for (i=0;i<length;i++){if(expression[i]>='0'&&expression[i]<='9'){n=expression[i]-'0';//字符型轉(zhuǎn)換為整型 while (expression[i+1]!='\0'){if (expression[i+1]>='0'&&expression[i+1]<='9') {n=n*10+expression[i+1]-'0';++i;}else break;}PushNumber(&Num,n);}else if (expression[i]=='+'||expression[i]=='-'||expression[i]=='*'||expression[i]=='/'||expression[i]=='^'||expression[i]=='('||expression[i]==')'){switch (expression[i]){case '+':if(sig.data[sig.top-1]!='+'&&sig.data[sig.top-1]!='-'&&sig.data[sig.top-1]!='*'&&sig.data[sig.top-1]!='/'&&sig.data[sig.top-1]!='^')//與棧頂元素的優(yōu)先級相比較, 高于時(shí)入棧,此處判斷是否入棧。 PushSign(&sig,'+');else{while (sig.top>0&&sig.data[sig.top-1]!='(')//如果棧不為空切不為左括號,則出棧 {PopSign(&sig,&es);Calculate(&Num,es);}PushSign(&sig,'+');}break;case '-':if(sig.data[sig.top-1]!='+'&&sig.data[sig.top-1]!='-'&&sig.data[sig.top-1]!='*'&&sig.data[sig.top-1]!='/'&&sig.data[sig.top-1]!='^')PushSign(&sig,'-');else{while (sig.top>0&&sig.data[sig.top-1]!='('){PopSign(&sig,&es);Calculate(&Num,es);}PushSign(&sig,'-');}break;case '*':if(sig.data[sig.top-1]!='*'&&sig.data[sig.top-1]!='/'&&sig.data[sig.top-1]!='^')PushSign(&sig,'*');else{while (sig.top>0&&sig.data[sig.top-1]!='('){PopSign(&sig,&es);Calculate(&Num,es);}PushSign(&sig,'*');}break;case '/':if(sig.data[sig.top-1]!='*'&&sig.data[sig.top-1]!='/'&&sig.data[sig.top-1]!='^')PushSign(&sig,'/');else{while (sig.top>0&&sig.data[sig.top-1]!='('){PopSign(&sig,&es);Calculate(&Num,es);}PushSign(&sig,'/');}break;case '^':if(sig.data[sig.top-1]!='^')PushSign(&sig,'^');else{while (sig.top>0&&sig.data[sig.top-1]!='('){PopSign(&sig,&es);Calculate(&Num,es);}PushSign(&sig,'^');}case '(':PushSign(&sig,'(');break;case ')':while (sig.data[sig.top-1]!='('){PopSign(&sig,&es);Calculate(&Num,es);}PopSign(&sig,&es);}}}while (sig.top>0){PopSign(&sig,&es);Calculate(&Num,es);}GetTopNumber(Num,&en);printf("%.0f\n",en);return 0; }void InitNumber(number *stack) {stack->top=0; }void GetTopNumber(number stack, float *e) {if(stack.top==0) return;else *e=stack.data[stack.top-1]; }void PushNumber(number *stack, float e) {if(stack->top>=maximum) return;else stack->data[stack->top++]=e; }void PopNumber(number *stack, float *e) {if(stack->top==0) return;else *e=stack->data[--stack->top]; }void InitSign(sign *stack) {stack->top=0; }void GetTopSign(sign stack, char *e) {if(stack.top==0) return;else *e=stack.data[stack.top-1]; }void PushSign(sign *stack, char e) {if(stack->top>=maximum) return;//棧滿 else {stack->data[stack->top]=e;stack->top++;} }void PopSign(sign *stack, char *e) {if(stack->top==0) return;else *e=stack->data[--stack->top]; }void Calculate(number *stack, char e)// 計(jì)算結(jié)果 {float num1,num2,result;PopNumber(stack, &num2);PopNumber(stack, &num1);switch (e){case '+':result=num1+num2;PushNumber(stack,result);break;case '-':result=num1-num2;PushNumber(stack,result);break;case '*':result=num1*num2;PushNumber(stack,result);break;case '/':if (num2==0) printf("表達(dá)式錯誤!");else{result=num1/num2;PushNumber(stack,result);break;}case '^':result=pow(num1,num2);PushNumber(stack,result);break;} }四、后綴表達(dá)式的邏輯和實(shí)現(xiàn)方式(逆波蘭表達(dá)式求值)
1.定義
如果每個操作符跟在它的兩個操作數(shù)之后,而不是兩個操作數(shù)之間,那么這個表達(dá)式就是后綴表達(dá),又稱為逆波蘭表達(dá)式,如:3 5 + 7 * 1 -
2.后綴表達(dá)式計(jì)算機(jī)求值
1.與前綴表達(dá)式類似,只是順序是從左至右:
2.從左至右掃描表達(dá)式,遇到數(shù)字時(shí),將數(shù)字壓入堆棧,遇到運(yùn)算符時(shí),彈出棧頂?shù)膬蓚€數(shù),其中先出棧的是右操作數(shù),后出棧的是左操作數(shù),
3.用運(yùn)算符對它們做相應(yīng)的計(jì)算(次頂元素 op 棧頂元素),并將結(jié)果入棧;
4.重復(fù)上述過程直到表達(dá)式最右端,最后運(yùn)算得出的值即為表達(dá)式的結(jié)果
3.例子
計(jì)算后綴表達(dá)式的值:1 2 3 + 4 × + 5 -
1)從左至右掃描,將1,2,3壓入棧;
2)遇到+運(yùn)算符,3和2彈出,計(jì)算2+3的值,得到5,將5壓入棧;
3)遇到4,將4壓入棧
4)遇到×運(yùn)算符,彈出4和5,計(jì)算5×4的值,得到20,將20壓入棧;
5)遇到+運(yùn)算符,彈出20和1,計(jì)算1+20的值,得到21,將21壓入棧;
6)遇到5,將5壓入棧;
7)遇到-運(yùn)算符,彈出5和21,計(jì)算21-5的值,得到16為最終結(jié)果
4.代碼實(shí)現(xiàn)
bool isNumber(char* token) {return strlen(token) > 1 || ('0' <= token[0] && token[0] <= '9'); }int evalRPN(char** tokens, int tokensSize) {int n = tokensSize;int stk[n], top = 0;for (int i = 0; i < n; i++) {char* token = tokens[i];if (isNumber(token)) {stk[top++] = atoi(token);} else {int num2 = stk[--top];int num1 = stk[--top];switch (token[0]) {case '+':stk[top++] = num1 + num2;break;case '-':stk[top++] = num1 - num2;break;case '*':stk[top++] = num1 * num2;break;case '/':stk[top++] = num1 / num2;break;}}}return stk[top - 1]; }atoi函數(shù)的用法在另一篇文章有體現(xiàn),其作用就是將字符串轉(zhuǎn)化為整數(shù)型。
五、相互轉(zhuǎn)換
1.中綴表達(dá)式轉(zhuǎn)化為前綴表達(dá)式
①算法描述
(1)首先構(gòu)造一個運(yùn)算符棧S1和一個儲存中間結(jié)果的棧S2。
(2)從右至左掃描中綴表達(dá)式
(3)如果是操作數(shù)時(shí),將其壓入S2。
(4)如果是運(yùn)算符,則與S1棧頂元素比較優(yōu)先級:
a) 如果S1為空,或棧頂運(yùn)算符為右括號“)”,則直接將此運(yùn)算符入棧;
b) 否則,若該運(yùn)算符優(yōu)先級比棧頂運(yùn)算符的較高或相等,也將運(yùn)算符壓入S1;
c) 否則,將S1棧頂?shù)倪\(yùn)算符彈出并壓入到S2中,再與S1中新的棧頂運(yùn)算符相比較
(5)遇到括號時(shí):
a) 如果是右括號“)”,則直接壓入S1;
b) 如果是左括號“(”,則依次彈出S1棧頂?shù)倪\(yùn)算符,并壓入S2,直到遇到右括號為止,此時(shí)將這一對括號 丟棄;
(6)重復(fù)步驟(2)至(5),直到表達(dá)式的最左邊;
(7)若表達(dá)式掃描完,將S1中剩余的運(yùn)算符依次出棧并壓入S2;
(8)依次將S2中的元素出棧,結(jié)果即為對應(yīng)的前綴表達(dá)式。
②例子
2.前綴表達(dá)式轉(zhuǎn)化為中綴表達(dá)式
?
3.中綴表達(dá)式轉(zhuǎn)化為后綴表達(dá)式
①算法描述
(1)首先構(gòu)造一個運(yùn)算符棧S1和一個儲存中間結(jié)果的棧或線性表S2。
(2)從左至右掃描中綴表達(dá)式
(3)如果是操作數(shù)時(shí),將其壓入S2。
(4)如果是運(yùn)算符,則與S1棧頂元素比較優(yōu)先級:
a) 如果S1為空,或棧頂運(yùn)算符為右括號“(”,則直接將此運(yùn)算符入棧;
b) 否則,若該運(yùn)算符優(yōu)先級比棧頂運(yùn)算符的較高或相等,也將運(yùn)算符壓入S1;
c) 否則,將S1棧頂?shù)倪\(yùn)算符彈出并壓入到S2中,再與S1中新的棧頂運(yùn)算符相比較
(5)遇到括號時(shí):
a) 如果是左括號“(”,則直接壓入S1;
b) 如果是右括號“)”,則依次彈出S1棧頂?shù)倪\(yùn)算符,并壓入S2,直到遇到右括號為止,此時(shí)將這一對括號丟棄;
(6)重復(fù)步驟(2)至(5),直到表達(dá)式的最右邊;
(7)若表達(dá)式掃描完,將S1中剩余的運(yùn)算符依次出棧并壓入S2;
(8)從左往右依次讀取S2中的元素,即為對應(yīng)的后綴表達(dá)式。
②例子
③代碼實(shí)現(xiàn)
//本程序只能處理有關(guān)運(yùn)算符+、-、*、/的中綴表達(dá)式,不能是÷或者×及其他運(yùn)算 //界限符只能是英文狀態(tài)的左右括號即'('、')',操作數(shù)只能是整數(shù) //本程序不會檢查輸入的中綴表達(dá)式是否正確,因此請您核驗(yàn)好自己的式子是否正確 #include<stdio.h> #include<string.h> //strlen的頭文件,用于判斷字符串長度 #include<stdlib.h> //malloc、free的頭文件 #define size 50//假定要轉(zhuǎn)換的中綴表達(dá)式的字符數(shù)在50個以內(nèi) typedef struct Linknode{ //定義鏈棧及結(jié)點(diǎn)char data; //數(shù)據(jù)域struct Linknode *next; //指針域 }*LiStack; bool InitStack(LiStack &S){ //鏈棧的初始化,不帶頭結(jié)點(diǎn)S=NULL; //剛開始沒有結(jié)點(diǎn)return true; } bool StackEmpty(LiStack S){ //判斷棧空return S==NULL; } bool Push(LiStack &S,char x){ //將元素x入棧Linknode *s=(Linknode *)malloc(sizeof(Linknode)); //創(chuàng)建新結(jié)點(diǎn)if(s==NULL) //內(nèi)存不足,創(chuàng)建失敗return false;s->data=x;s->next=S; //將結(jié)點(diǎn)s作為鏈棧的棧頂結(jié)點(diǎn)S=s; //棧頂指針S指向結(jié)點(diǎn)sreturn true; } bool Pop(LiStack &S,char &x){ //棧頂元素出棧,將值賦給xif(S==NULL)return false; //棧空則返回NULLx=S->data;Linknode *p=S;S=S->next;free(p);return true; } int main(){char temp,a[size],b[size]; //靜態(tài)數(shù)組a、b分別存放要轉(zhuǎn)換的中綴表達(dá)式和轉(zhuǎn)換后的后綴表達(dá)式,字符變量temp存放彈出的棧頂元素scanf("%s",&a); //需要您輸入中綴表達(dá)式LiStack S;//初始化一個棧,用于保存括號和暫時(shí)還不能確定運(yùn)算順序的運(yùn)算符InitStack(S); //初始化鏈棧int i,j,length=strlen(a); //length為輸入的中綴表達(dá)式的總長度,i、j分別為靜態(tài)數(shù)組a、b的索引下標(biāo)for(i=j=0;i<length;i++){if(a[i]>=48 && a[i]<=57){ //若當(dāng)前字符是數(shù)字,字符0-9的ACSII碼范圍是[48,57]b[j++]=a[i];if(a[i+1]=='+'||a[i+1]=='-'||a[i+1]=='*'||a[i+1]=='/') //若下一個字符是運(yùn)算符,即+、-、*、/,則b加一個空格,以免不同的操作數(shù)混在一起b[j++]=' ';}else if(a[i]=='(')Push(S,a[i]); //若當(dāng)前字符是左括號則直接入棧else if(a[i]==')'){ //若當(dāng)前字符是右括號while(StackEmpty(S)==0){ //棧非空則不斷彈出棧內(nèi)字符并加入后綴表達(dá)式Pop(S,temp);if(temp=='(') //直到彈出左括號停止,注意這個(不加入后綴表達(dá)式break;b[j++]=temp;b[j++]=' '; //加一個空格,從而將字符隔開}}else switch(a[i]){ //若當(dāng)前字符是運(yùn)算符case '*': case '/':{while(StackEmpty(S)==0){ //若棧非空,則彈出棧中優(yōu)先級高于或等于當(dāng)前運(yùn)算符的所有運(yùn)算符,并將這些運(yùn)算符加入后綴表達(dá)式Pop(S,temp);if(temp=='/'||temp=='*'){b[j++]=temp;b[j++]=' '; //加一個空格,從而將字符隔開}else if(temp=='('||temp=='-'||temp=='+'){//若棧頂元素是左括號或者是優(yōu)先級低于當(dāng)前字符的運(yùn)算符,則將棧頂元素入棧Push(S,temp);break;}}Push(S,a[i]); //把當(dāng)前字符入棧break;}case '-': case '+':{while(StackEmpty(S)==0){ //若棧非空,則彈出棧中優(yōu)先級高于或等于當(dāng)前運(yùn)算符的所有運(yùn)算符,并將這些運(yùn)算符加入后綴表達(dá)式Pop(S,temp);if(temp=='('){//若棧頂元素是左括號,則將棧頂元素入棧Push(S,temp);break;}else if(temp=='/'||temp=='*'||temp=='-'||temp=='+'){b[j++]=temp;b[j++]=' '; //加一個空格,從而將字符隔開}}Push(S,a[i]); //把當(dāng)前字符入棧break;}}}while(StackEmpty(S)==0){ //棧非空時(shí)依次彈出棧頂元素并加入后綴表達(dá)式Pop(S,temp);b[j++]=temp;b[j++]=' '; //加一個空格,從而將字符隔開}printf("結(jié)果是:\n");for(i=0;i<j;i++) //j是數(shù)組中下一個可以插入元素的位置下標(biāo),因此b中存放字符的索引區(qū)間為[0,j-1]printf("%c",b[i]); //輸出b中的元素printf("\n");return 0; }4.后綴表達(dá)式轉(zhuǎn)化為中綴表達(dá)式
同上前綴表達(dá)式轉(zhuǎn)中綴表達(dá)式的原理一致,區(qū)別在于前綴是從右往左掃描,后綴表達(dá)式是從左往右掃描。
六、總結(jié)
1.常用表達(dá)式求值分析
①方法
常見的方法有兩種,一種是中綴表達(dá)式求值,一種是后綴表達(dá)式求值
②優(yōu)缺點(diǎn)
中綴表達(dá)式:符合人的習(xí)慣,但是在計(jì)算機(jī)中計(jì)算時(shí)要考慮其優(yōu)先級和括號的關(guān)系,實(shí)現(xiàn)起來比較麻煩
后綴表達(dá)式:在計(jì)算機(jī)中實(shí)現(xiàn)時(shí)不需要考慮優(yōu)先級和括號,因?yàn)楹缶Y表達(dá)式已經(jīng)將其解決了,但是不符合人的習(xí)慣
2.相互轉(zhuǎn)換分析
①關(guān)于前綴的轉(zhuǎn)換是從右向左掃描的
②關(guān)于后綴的轉(zhuǎn)換是從左向右轉(zhuǎn)換的
3.總結(jié)
不管是怎么樣轉(zhuǎn)換和怎么樣計(jì)算,思路理解起來都沒那么難,難就難在代碼的實(shí)現(xiàn)上,雖然我賦有代碼,但是這些代碼是我從別的博客上整理來的,由于太多了不記得那是哪了,要是涉及侵權(quán)之類的請聯(lián)系我,我立馬刪除。
代碼有了、思路也有了,關(guān)鍵就是代碼的實(shí)現(xiàn)上了,這個一定要自己敲,就算是照著敲一遍也比復(fù)制粘貼的好,邊敲邊理解,到最后看能不能用自己的思路敲出來。
作為碼農(nóng),代碼還是要多敲的,之前忽略了,現(xiàn)在就要惡補(bǔ)了,目前每天刷兩道Leetcode來彌補(bǔ)。
代碼的實(shí)踐還是很重要的!
代碼的實(shí)踐還是很重要的!
代碼的實(shí)踐還是很重要的!
花了一天的時(shí)間整理出來的,希望對個位有幫助,有什么不理解的也可以留言,或者加我QQ:2417734199。
總結(jié)
以上是生活随笔為你收集整理的表达式求值(最详细分析+代码实现+表达式之间的相互转换)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机的启动过程———《x86汇编语言:
- 下一篇: 显存文本模式详解 ———《x86汇编语言