Cálculos en polaco inverso (incluidas funciones matemáticas y números de coma flotante)

这里的实现较为简单,也只能处理没有嵌套的表达式的情况。
相对完善的代码请见另一篇博客--完整的逆波兰式计算,修正了函数嵌套时出现的问题,调整的一定的
处理方式,使得代码更加简洁。不过大体思路与本代码基本相同。


//本代码提供的测试数据
//9.4+(3.27-1.05)*3.44+10/2.1+cos(0.52)+ln(3)/log(5)+3^2


#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 解决了带有数学函数的式子的逆波兰式的转化,利用索引数组和原始栈来共同表示这个逆波兰式
//缺陷 -- 在数学函数中可能还是含有数学函数,本段代码未处理这种嵌套的情况 

 

Supongo que te gusta

Origin blog.csdn.net/wyzworld/article/details/82931020
Recomendado
Clasificación