1. 程式人生 > >逆波蘭式的計算(含有數學函式和浮點數)

逆波蘭式的計算(含有數學函式和浮點數)

#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 解決了帶有數學函式的式子的逆波蘭式的轉化,利用索引陣列和原始棧來共同表示這個逆波蘭式
//缺陷 -- 在數學函式中可能還是含有數學函式,本段程式碼未處理這種巢狀的情況