逆波蘭式的計算(含有數學函式和浮點數)
阿新 • • 發佈:2018-12-13
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> #define MAX 100 #define MARK 65535 typedef struct { char data[MAX]; int top; }SqStack; SqStack *initStack(void); void destroyStack(SqStack *S); void creatStack(SqStack *S); void printStack(SqStack *S); void changeRpn(SqStack *S,SqStack *rpn); //先形式上轉化為逆波蘭式 int rpnStack(SqStack *rpn,double num[MAX],int loc[MAX],int stack[MAX],char *mark); //產生一個形式逆波蘭式存在 stack 中 double conduct(char mark[MAX],double num[MAX],int stack[MAX],int stacklen); //利用逆波蘭式計算算式結果並返回 //處理逆波蘭式的方法 //規則:從左到右遍歷表示式的每個數字和符號,遇到是數字就進棧,遇到是符號,就將處於棧頂兩個數字出棧,進行運算,運算結果進棧,一直到最終獲得結果。 double conduct(char mark[MAX],double num[MAX],int stack[MAX],int stacklen) { double temp[MAX]; //用來存放中間計算過程的棧 int j = 0; //索引 temp int p = 0; //索引 符號棧 mark //int marklen = strlen(mark); for(int i = 0;i<stacklen;i++) //遍歷逆波蘭式求值 { if(stack[i] == MARK) //如果是操作符,取棧頂兩個元素進行計算並存入棧中 { double x = temp[j-1]; //得到棧頂的兩個數 (記住 是前一個數對後一個數操作 ) double y = temp[j-2]; j = j-2; if(mark[p] == '+') { temp[j++] = x+y; } else if(mark[p] == '-') { temp[j++] = y-x; } else if(mark[p] == '*') { temp[j++] = x*y; } else if(mark[p] == '/') { temp[j++] = y/x; } p++; //符號棧指標下移 } else //若是數字則直接進臨時棧 { temp[j++] = num[stack[i]]; } } return temp[0]; //返回運算結果 } int rpnStack(SqStack *rpn,double num[MAX],int loc[MAX],int stack[MAX],char *mark) { int lenloc = 0; for(int i = 0;i<=rpn->top;i++) //定位出空格的位置 { if(rpn->data[i] == ' ') loc[lenloc++] = i; } //將計算需要用到的數字全都存放在 num 陣列中 int count1 = 0,count2 = 0; //兩個 count 用來記兩個空格之間的差值,前面ver2時提到,兩個空格之間有且僅有一個運算元 char a[MAX]; //用來將串轉化成浮點數的臨時陣列 -- 記住,每次使用前都要初始化 int numlen = 0; //直接用 numlen 記出 運算元的個數 for(int i = 0;i<lenloc;i++) { count1 = loc[i]; //先用 count1 定位到後一個空格的位置 memset(a,0,sizeof(a)); //初始化 a 陣列 if(count1-count2>=1) //如果兩個空格之間有數字 -- 防止多個空格連續的情況 { int p = 0; //用來往 a 數組裡存字元的下標,每次用完要重置 0 while(count2<count1) { if(rpn->data[count2] == '.' || (rpn->data[count2] >= '0'&&rpn->data[count2] <= '9')) //如果是數字或小數點 -- 可能是乘方或存運算元,需要區分 { int k = count2; while(rpn->data[k]>='0'&&rpn->data[k]<='9' || rpn->data[k] == '.') //先找下一個非數字和小數點的字元 k++; if(rpn->data[k] == '^') //如果是乘方號 { k = count2; //重置 k 到第一個數 while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') // ^ 號之前的是底數 a[p++] = rpn->data[k++]; double x = atof(a); k++; //k 加 1 越過 ^ 號 memset(a,0,sizeof(a)); //重置 a 陣列 p = 0; while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') //^ 號後面的是指數 a[p++] = rpn->data[k++]; double y = atof(a); if(p>0) num[numlen++] = pow(x,y); //計算 x 的 y 次方,存入 num 中 break; //找到一個運算元,跳出當前迴圈 ,防止陷入重複無用的迴圈,增加運算元 } else //否則就說明是純數字,直接入 a 然後轉化成浮點數存進 num 即可 { k = count2; while(rpn->data[k]>='0' && rpn->data[k] <= '9' || rpn->data[k] == '.') //將這一段完整的數字給找到 a[p++] = rpn->data[k++]; if(p>0) //如果 a 數組裡有新存的數 num[numlen++] = atof(a); break; //找到一個運算元,跳出當前迴圈 ,防止陷入重複無用的迴圈,增加運算元 } } else if(rpn->data[count2] == 'c' && rpn->data[count2+1] == 'o') //計算 cos { int k = count2 + 4; //k 定位到 cos 中的第一個數字 int q = 0; memset(a,0,sizeof(a)); while(rpn->data[k] != ')') //在右括號之前的都是運算元 { a[q++] = rpn->data[k++]; } if(q>0) num[numlen++] = cos(atof(a)); //計算 cos 的值,作為一個運算元存入 num ,剩下幾個數學函式的計算類似 break; } else if(rpn->data[count2] == 's' && rpn->data[count2+1] == 'i') { int k = count2 + 4; int q = 0; memset(a,0,sizeof(a)); while(rpn->data[k] != ')') { a[q++] = rpn->data[k++]; } if(q>0) num[numlen++] = sin(atof(a)); break; } else if(rpn->data[count2] == 'l' && rpn->data[count2+1] == 'o') { int k = count2 + 4; int q = 0; memset(a,0,sizeof(a)); while(rpn->data[k] != ')') { a[q++] = rpn->data[k++]; } if(q>0) num[numlen++] = log10(atof(a)); break; } else if(rpn->data[count2] == 'l' && rpn->data[count2+1] == 'n') { int k = count2 + 3; int q = 0; memset(a,0,sizeof(a)); while(rpn->data[k] != ')') { a[q++] = rpn->data[k++]; } if(q>0) num[numlen++] = log(atof(a)); break; } else //如果是空格或操作符,檢查下一個 count2++; } } count2 = count1; //跟新前一個空格位 } //建立形式逆波蘭式的棧 stack for(int i = 0;i<MAX;i++) //初始化 stack stack[i] = -1; int count = 0; //記是第幾個運算數 int u = 0; //用來給 stack 記下標 for(int i = 0;i<=rpn->top;i++) { if(rpn->data[i]>='0' && rpn->data[i] <= '9' || rpn->data[i] == '.') //如果是數字 { int j = i; while(rpn->data[j]>='0'&&rpn->data[j]<='9' || rpn->data[j] == '.') //先找下一個非數字和小數點的字元 j++; //printf("rpn->data[%d] == %c \n",j,rpn->data[j]); if(rpn->data[j] == '^') //最近的非數字和小數點的字元為 ^ { j = i; while(rpn->data[j]>='0' && rpn->data[j] <= '9' || rpn->data[j] == '.' || rpn->data[j] == '^') //將包含乘方號在內的下標同一為一個運算元下標 { stack[u++] = count; j++; } count++; //運算元個數加 1 i = j-1; //更新主迴圈下標,越過當前的數字 -- 值得一提的是 i 是更新為 j-1 因為在上一個迴圈結束後 j 走到了非運算元的位置,將 i 更新為 j-1在下一次 //迴圈開始時,i加 1 會正好移動到這個非運算元的位置,如果更新 i 為 j ,則會向前多進一位,可能會造成某些錯誤 } else //否則是存數字 { j = i; while(rpn->data[j]>='0' && rpn->data[j] <= '9' || rpn->data[j] == '.') //將該浮點數佔位同一為當前運算元下標 { stack[u++] = count; j++; } count++; //運算元個數加 1 i = j-1; //更新主迴圈下標,越過當前的數字 } } else if(rpn->data[i] == 'c' && rpn->data[i+1] == 'o') //將含義數學符號在內的一部分記為一個運算元下標,下面幾個數學函式類似 { int j = i; while(rpn->data[j] != ')') //一直到後面的一個括號都需要更新 stack 為一個運算元的下標 { stack[u++] = count; j++; } stack[u++] = count; count++; i = j; //更新主迴圈下標,越過當前的數學函式 這裡更新為 j 正好可以越過右括號 } else if(rpn->data[i] == 's' && rpn->data[i+1] == 'i') { int j = i; while(rpn->data[j] != ')') { stack[u++] = count; j++; } stack[u++] = count; count++; i = j; } else if(rpn->data[i] == 'l' && rpn->data[i+1] == 'o') { int j = i; while(rpn->data[j] != ')') { stack[u++] = count; j++; } stack[u++] = count; count++; i = j; } else if(rpn->data[i] == 'l' && rpn->data[i+1] == 'n') { int j = i; while(rpn->data[j] != ')') { stack[u++] = count; j++; } stack[u++] = count; count++; i = j; } else if(rpn->data[i] == ' ') //空格則跳過 ; else //如果是操作符,stack 棧存放一個特殊的數字 MARK(符號) stack[u++] = MARK; } //刪除rpn陣列中重複的數字類元素 //int len = rpn->top; int i = 0; while(i<=rpn->top) { if(stack[i] == -1) //如果是 -1 說明是空格需要捨棄, { int j; for(j=i+1;stack[j]==-1;j++); //找到下一個不是空格 (-1) 的位置 int k = i; while(k<rpn->top) //從當前位置開始覆蓋 stack[k++] = stack[j++]; } else if(stack[i] != MARK) //如果不是操作符,直接覆蓋掉相同的數字 { int j; for(j=i+1;stack[j] == stack[i];j++); int k = i+1; while(k<rpn->top) stack[k++] = stack[j++]; } i++; } //這個迴圈是必要的,由於可能存在多個連續的空格,覆蓋完當前的 -1 時,迴圈下標會下移一位,造成數字的冗餘,該迴圈處理這些冗餘數字 //相當於二次刪除陣列中重複元素,相關程式碼基本和上面相同 i = 0; while(i<=rpn->top) { if(stack[i] != MARK) { int j; for(j=i+1;stack[j] == stack[i];j++); int k = i+1; while(k<=rpn->top) stack[k++] = stack[j++]; } i++; } int stacklen = 0; //求 stack 棧的長度 -- 運算元加操作符 for(int p = 0;p<=rpn->top;p++) if(stack[p] == MARK) //得到操作符個數 stacklen++; stacklen = stacklen+numlen; printf("最後得到的逆波蘭式:\n"); i = 0; int k = 0; for(int j = 0;j<stacklen;j++) //聯合輸出最終逆波蘭式 { if(stack[j] == MARK) { while(rpn->data[i] >= '0' && rpn->data[i] <= '9' || rpn->data[i] == '.' || rpn->data[i] == '^' || rpn->data[i] == 'c' || rpn->data[i] == 'o' || rpn->data[i] == 's' || rpn->data[i] == 'i' || rpn->data[i] == 'n' || rpn->data[i] == 'l' || rpn->data[i] == 'g' || rpn->data[i] == ' ' || rpn->data[i] == '(' || rpn->data[i] == ')') //是這些符號的時候原rpn->data 下標遞增 i++; printf("%c ",rpn->data[i]); //列印操作符 mark[k++] = rpn->data[i]; i++; } else //數字就直接列印 printf("%.2f ",num[stack[j]]); } return stacklen; //返回形式逆波蘭式的長度 } void changeRpn(SqStack *S,SqStack *rpn) { SqStack T; T.top = -1; int t = 0; while(t<=S->top) { if(S->data[t]>='0'&&S->data[t]<='9' || S->data[t] == '.') //這裡不能是數字就直接進棧了,因為乘方號在數字之間 { int j = t; while(S->data[j]>='0'&&S->data[j]<='9' || S->data[j] == '.') //先找下一個非數字和小數點的字元 j++; if(S->data[j] == '^') //如果是乘方號 ,連帶乘方號入棧 { j = t; while(S->data[j]>='0' && S->data[j] <= '9' || S->data[j] == '.' || S->data[j] == '^') rpn->data[++rpn->top] = S->data[j++]; t = j-1; } else //否則按數字進棧即可 rpn->data[++rpn->top] = S->data[t]; } else if(S->data[t]>='a' && S->data[t] <= 'z') { int j = t; while(S->data[j] != ')') { rpn->data[++rpn->top] = S->data[j]; j++; } rpn->data[++rpn->top] = S->data[j]; rpn->data[++rpn->top] = ' '; t = j; } else if(S->data[t]=='('||S->data[t]==')') { rpn->data[++rpn->top] = ' '; //當遇到符號時,說明一個數字已經結束,在逆波蘭式後面加一個空格用來區分數字 if(S->data[t]=='(') //左括號直接進棧 { T.data[++T.top] = S->data[t]; } else if(S->data[t] == ')') //右括號則出棧到前一個左括號 { while(T.data[T.top]!='(' && T.top>=0) { rpn->data[++rpn->top] = T.data[T.top--]; } T.top--; //略去左括號 } } else { rpn->data[++rpn->top] = ' '; //當遇到符號時,說明一個數字已經結束,在逆波蘭式後面加一個空格用來區分數字 if(S->data[t]=='+'||S->data[t]=='-') { if(T.top == -1||T.data[T.top] == '(') //加減符號,當前符號棧沒有元素則直接進棧,或當前棧頂是左括號則進棧 T.data[++T.top] = S->data[t]; else //否則出棧至前一個左括號或棧空,再將其入棧 { while(T.top>=0&&T.data[T.top]!='(') { rpn->data[++rpn->top] = T.data[T.top--]; } T.data[++T.top] = S->data[t]; } } else //乘除直接進棧 T.data[++T.top] = S->data[t]; } t++; } while(T.top>=0) //將符號棧中剩下的元素賦值給逆波蘭式棧 rpn->data[++rpn->top] = T.data[T.top--]; rpn->data[++rpn->top] = ' '; //在最後加一空格做限定最後一個數的邊界 } SqStack *initStack(void) { SqStack *S = (SqStack *)malloc(sizeof(SqStack)); if(!S) exit(0); S->top = -1; return S; } void destroyStack(SqStack *S) { free(S->data); free(S); } void printStack(SqStack *S) { int t = 0; while(t<=S->top) printf("%c",S->data[t++]); printf("\n"); } void creatStack(SqStack *S) { char ch; while(scanf("%c",&ch)==1&&ch!='\n') S->data[++S->top] = ch; } int main(void) { freopen("逆波蘭式ver3.txt","r",stdin); SqStack *S = initStack(); creatStack(S); printStack(S); //double fun[MAX]; //儲存計算函式的值 //conduct1(S,fun); //只是作為測試的一部,實際計算未用到 SqStack *rpn = initStack(); //先得到一個含字元的逆波蘭式,後續操作都在這個式子上進行 changeRpn(S,rpn); //printStack(rpn); //列印檢查 double num[MAX]; //儲存運算元 int loc[MAX]; //定位 rpn->data 中的空格 ,用來抽取運算元 int stack[MAX]; //形式逆波蘭式棧,用於最後的計算 char mark[MAX]; //存放操作符 ( 再遍歷 rpn->data 的到操作符太費事 ) int stacklen = rpnStack(rpn,num,loc,stack,mark); //得到 stack 形式逆波蘭式,同時返回其長度,便於後續計算 printf("\n"); double result = conduct(mark,num,stack,stacklen); //計算表示式的值 printf("result = %.2f\n",result); destroyStack(S); //銷燬棧 destroyStack(rpn); return 0; } //cos sin 是按弧度制計算的 //ver3 解決了帶有數學函式的式子的逆波蘭式的轉化,利用索引陣列和原始棧來共同表示這個逆波蘭式 //缺陷 -- 在數學函式中可能還是含有數學函式,本段程式碼未處理這種巢狀的情況