逆波兰(Reverse Polish Notation)四则运算表达式求值 《大话数据结构》

逆波兰(Reverse Polish Notation)四则运算表达式求值


最近在看大话数据结构这本书,感觉这本书确实很不错,深入浅出,算是算法入门难得的佳作!看得出作者写这本书也花了不少心血!

在第四章介绍栈之后介绍了栈的应用——四则表达式求值,但是没有代码,技痒,故花了一个晚上写了如下代码。

为了避免将来再看自己的程序看不懂是个啥思路,所以在代码里面做了充分的注释。

我看网上逆波兰的程序都是操作符和操作数各一个栈,然后一步到位的。鉴于初学,所以将逆波兰表达式和表达式计算分两个函数来写了。


栈在程序中的作用到底是什么?用书上的话能很好地回答这个问题:

1、将中缀表达式(标准的四则运算表达式)转换为后缀表达式(逆波兰表达式)(栈用来进出运算符号)

2、将后缀表达式进行计算得出结果(栈用来进出运算的操作数)


好吧,详情见代码了。如果发现代码有什么问题,还请不吝赐教。


#include <iostream>
#include <string>
#include <sstream>

using namespace std;

//操作符链栈及其节点
typedef struct charNode
{
	char data;
	struct charNode * next;
}charNode, *charptr;

typedef struct charstack
{
	charptr top;
	int count;
}charstack;

bool Emptystack(charstack * S)
{
	return (S->top == NULL);
}

void Push(charstack *S, char e)
{
	charNode *one = new charNode;
	one->data = e;
	one->next = S->top;
	S->top = one;
	S->count ++ ;
}

bool Pop(charstack *S, char *e)
{
	if (Emptystack(S))
		return false;
	*e = S->top->data;
	charptr p = S->top;
	S->top = S->top->next;
	S->count --;
	delete p;
	return true;
}

//数字链栈及其节点
typedef struct doubleNode
{
	double data;
	struct doubleNode * next;
}doubleNode, *doubleptr;

typedef struct doublestack
{
	doubleptr top;
	int count;
}doublestack;

bool Emptystack(doublestack * S)
{
	return (S->top == NULL);
}

void Push(doublestack *S, double e)
{
	doubleNode *one = new doubleNode;
	one->data = e;
	one->next = S->top;
	S->top = one;
	S->count ++ ;
}

bool Pop(doublestack *S, double *e)
{
	if (Emptystack(S))
		return false;
	*e = S->top->data;
	doubleptr p = S->top;
	S->top = S->top->next;
	S->count --;
	delete p;
	return true;
}

// 将标准四则运算表达式(也即中缀运算表达式)转换成后缀运算表达式
string Reverse(string str)
{
	string out = "";
	string output = "";
	charstack *cha = new charstack;
	cha->count = 0;
	cha->top = NULL;
	int i = 0;
	bool flag = false; // flag = false 表示数字是连续的,比如321是一个操作数;否则则认为中间有操作符
	for(i=0; i<str.length(); i++)
	{
		if(str[i]>= '0' && str[i] <= '9')
		{
			if(flag == false)
				out += str[i];
			else
			{
				out += " ";
				out += str[i];
				flag = false;    // flag 重新置成false
			}
		}
		else if(str[i]=='.')
			out += str[i];   //如果是小数点,直接输出即可
		else if(str[i]=='(' || str[i]=='*' || str[i]=='/')  //如果是左括号及乘除号,直接进栈
		{
			flag = true;
			Push(cha,str[i]);		
		}
		else if(str[i]==')')  //如果是右括号,一直出栈,直到遇到左括号。在这里左括号出栈就不应该放回去了。
		{
			flag = true;
			char * temp = new char;
			Pop(cha,temp);	     //这里既然是右括号,说明之前必定入栈了左括号,所以栈一定不为空,因此不必检测栈是否为空
			while(*temp != '(')
			{
				out += " ";
				out += *temp;   // 符号前后都加上空格,以便后面根据空格分隔字符串,区分操作数和操作符
				out += " ";
				Pop(cha,temp);	
			}
		}
		else if(str[i]=='+' || str[i]=='-')  //如果是加减号,则将所有的符号出栈,除非遇到了左括号
		{
			flag = true;
			char * temp = new char;
			if(!Emptystack(cha))   //如果栈不为空则出栈
			{				
				while(!Emptystack(cha))  
				{
					Pop(cha,temp);
					if(*temp=='(')  //如果最后遇到了左括号,还得再入栈回去,要不然右括号来了又怎么找到左括号呢?同时还应该跳出循环
					{	
						Push(cha,*temp);
						break;   //跳出while循环
					}
					out += " ";
					out += *temp;
					out += " ";
				}
			}
			// 出栈后还得将当前符号入栈
			Push(cha,str[i]);
		}
	}
	// 最后表达式已经全部处理了,此时应该将栈中的所有符号全部出栈
	{
		char * temp = new char;  //加上大括号,让*temp变成局部变量
		while(!Emptystack(cha))
		{
			Pop(cha, temp);
			out += " ";
			out += *temp;
			out += " ";
		}
	}

	// 由于之前符号的前后都加了一个空格,如果两个符号接连输出则符号间就会有两个空格,可能会导致后面不方便处理。
	// 在这里我们对out字符串进行一下整理工作,去掉多余的空格
	{
		//cout <<out<<endl;
		bool space = false;   // 当当前处理的字符前一个字符是空格的时候为true
		for(int i=0;i<out.length();i++)
		{			
			if(space == false)     //如果前一个字符不是空格,就直接将当前字符复制到output中
				output += out[i];		
			else if(space == true && out[i] != ' ')  //如果前一个字符是空格,但当前字符不是空格,就将当前字符复制到output中
				output += out[i];
			//出现两个空格的情况,后一个空格就不复制到output中去就行了。

			//最后检测当前字符是否空格,以用于对下一个字符进行处理。
			//这一句话一定不能放在上面判断语句之前
			//这个很好理解,就假设当前字符串为空格,如果放到上面判断语句之前,想想会发生什么状况
			if(out[i] == ' ')
				space = true;
			else
				space = false;		
		}	
	}
	return output;
}

// 根据后缀表达式字符串,计算运算结果
double Calculate(string str)
{
	double * result = new double;
	doublestack * dou = new doublestack;
	
	const char *split = " "; //按照空格对字符串进行分割
	char * p = strtok(const_cast<char *>(str.c_str()),split);
	while (p)
	{
		if(*p == '+' || *p == '-' || *p == '*' || *p == '/')
		{
			if (Emptystack(dou))   
			{
				//如果已经到符号位了,而数字栈却为空,说明后缀表达式有误。
				//分两种情况考虑,一种是错误的或者不规范的输入,另一种是我的程序本身对某些情况考虑不足
				printf("The Reverse Polish Notation is wrong! Please check the expression which you input.\n");
				return 0.0;			
			}
			double *a = new double; //用于存储从栈中弹出的两个操作数
			double *b = new double;  
			Pop(dou,a);
			Pop(dou,b);
			double c; //用于存储两个操作数操作结果
			switch(*p)//在这里减号和除号要注意操作数的顺序!是后出栈的减去或除以先出栈的,也即*b-/*a
			{        
				case '+':
					c = (*b) + (*a);
					break;
				case '-':
					c = (*b) - (*a);
					break;
				case '*':
					c = (*b) * (*a);
					break;
				case '/':
					c = (*b) / (*a);
					break;				
			}
			Push(dou,c);  //将操作结果入栈
		
		}
		else  // 否则则认为是操作数,这些操作数必须先转换成double类型再入栈!
		{
			double num;
			istringstream temp(p);
			temp>>num;
			Push(dou,num);		
		}
		p=strtok(NULL,split);
	
	}

	Pop(dou,result);  //将最后的运行结果出栈
	return *result;
}

int main()
{	
	string original;     //运算表达式,也即中缀表达式
	string nipolish;     //后缀表达式,也即逆波兰表达式
	double result;
	cout<<"Please input a expression :"<<endl;
	//cin>>original;        //cin认为一个空格是输入的结束,getline是可以输入空格的,以回车作为结束符
	getline(cin,original);  //用getline 而不用cin>>输入的好处是可以接受空格,例如9.3 / 3这样的输入是可以接受的,可以处理的。
	nipolish = Reverse(original);
	cout<<"The Reverse Polish Notation is:"<<endl<<nipolish<<endl;
	result = Calculate(nipolish);
	cout<<"The result of expression is "<<result<<endl;

	return 0;
}


最后附上几组测试用例:

9+(3-1)*3+10/2 = 20

9.7+3.3+100/25+3.7*4+(7-2.3)*5 = 55.3

(4-6)*3 = -6

自己定义栈还是挺麻烦的,下面使用STL实现的逆波兰算法。

#include <iostream>
#include <stack>
#include <string>
#include <sstream>

using namespace std;


// 将标准四则运算表达式(也即中缀运算表达式)转换成后缀运算表达式
string Reverse(string str)
{
	string out = "";
	string output = "";
	stack<char>charstack;
	int i = 0;
	bool flag = false; // flag = false 表示数字是连续的,比如321是一个操作数;否则则认为中间有操作符
	for(i=0; i<str.length(); i++)
	{
		if(str[i]>= '0' && str[i] <= '9')
		{
			if(flag == false)
				out += str[i];
			else
			{
				out += " ";
				out += str[i];
				flag = false;    // flag 重新置成false
			}
		}
		else if(str[i]=='.')
			out += str[i];   //如果是小数点,直接输出即可
		else if(str[i]=='(' || str[i]=='*' || str[i]=='/')  //如果是左括号及乘除号,直接进栈
		{
			flag = true;
			charstack.push(str[i]);	
		}
		else if(str[i]==')')  //如果是右括号,一直出栈,直到遇到左括号。在这里左括号出栈就不应该放回去了。
		{
			flag = true;
			char temp = charstack.top();
			charstack.pop();//这里既然是右括号,说明之前必定入栈了左括号,所以栈一定不为空,因此不必检测栈是否为空    
			while(temp != '(')
			{
				out += " ";
				out += temp;   // 符号前后都加上空格,以便后面根据空格分隔字符串,区分操作数和操作符
				out += " ";
				temp = charstack.top();
				charstack.pop();
			}
		}
		else if(str[i]=='+' || str[i]=='-')  //如果是加减号,则将所有的符号出栈,除非遇到了左括号
		{
			flag = true;
			char temp;
			if(!charstack.empty())   //如果栈不为空则出栈
			{				
				while(!charstack.empty())  
				{
					temp = charstack.top();
					charstack.pop();
					if(temp=='(')  //如果最后遇到了左括号,还得再入栈回去,要不然右括号来了又怎么找到左括号呢?同时还应该跳出循环
					{	
						charstack.push(temp);
						break;   //跳出while循环
					}
					out += " ";
					out += temp;
					out += " ";
				}
			}
			// 出栈后还得将当前符号入栈
			charstack.push(str[i]);
		}
	}
	// 最后表达式已经全部处理了,此时应该将栈中的所有符号全部出栈
	{
		char temp;  //加上大括号,让*temp变成局部变量
		while(!charstack.empty())
		{
			temp = charstack.top();
			charstack.pop();
			out += " ";
			out += temp;
			out += " ";
		}
	}

	// 由于之前符号的前后都加了一个空格,如果两个符号接连输出则符号间就会有两个空格,可能会导致后面不方便处理。
	// 在这里我们对out字符串进行一下整理工作,去掉多余的空格
	{
		//cout <<out<<endl;
		bool space = false;   // 当当前处理的字符前一个字符是空格的时候为true
		for(int i=0;i<out.length();i++)
		{			
			if(space == false)     //如果前一个字符不是空格,就直接将当前字符复制到output中
				output += out[i];		
			else if(space == true && out[i] != ' ')  //如果前一个字符是空格,但当前字符不是空格,就将当前字符复制到output中
				output += out[i];
			//出现两个空格的情况,后一个空格就不复制到output中去就行了。

			//最后检测当前字符是否空格,以用于对下一个字符进行处理。
			//这一句话一定不能放在上面判断语句之前
			//这个很好理解,就假设当前字符串为空格,如果放到上面判断语句之前,想想会发生什么状况
			if(out[i] == ' ')
				space = true;
			else
				space = false;		
		}	
	}
	return output;
}

// 根据后缀表达式字符串,计算运算结果
double Calculate(string str)
{
	double result;
	stack<double>doublestack;
	const char *split = " "; //按照空格对字符串进行分割
	char * p = strtok(const_cast<char *>(str.c_str()),split);
	while (p)
	{
		if(*p == '+' || *p == '-' || *p == '*' || *p == '/')
		{
			if (doublestack.empty())   
			{
				//如果已经到符号位了,而数字栈却为空,说明后缀表达式有误。
				//分两种情况考虑,一种是错误的或者不规范的输入,另一种是我的程序本身对某些情况考虑不足
				printf("The Reverse Polish Notation is wrong! Please check the expression which you input.\n");
				return 0.0;			
			}
			double a; //用于存储从栈中弹出的两个操作数
			double b; 
			a = doublestack.top();
			doublestack.pop();
			b = doublestack.top();
			doublestack.pop();
			double c; //用于存储两个操作数操作结果
			switch(*p)//在这里减号和除号要注意操作数的顺序!是后出栈的减去或除以先出栈的,也即*b-/*a
			{        
				case '+':
					c = (b) + (a);
					break;
				case '-':
					c = (b) - (a);
					break;
				case '*':
					c = (b) * (a);
					break;
				case '/':
					c = (b) / (a);
					break;				
			}
			doublestack.push(c);  //将操作结果入栈
		
		}
		else  // 否则则认为是操作数,这些操作数必须先转换成double类型再入栈!
		{
			double num;
			istringstream temp(p);
			temp>>num;
			doublestack.push(num);	
		}
		p=strtok(NULL,split);
	
	}
	result = doublestack.top();//将最后的运行结果出栈
	doublestack.pop();  
	return result;
}

int main()
{	
	string original;     //运算表达式,也即中缀表达式
	string nipolish;     //后缀表达式,也即逆波兰表达式
	double result;
	cout<<"Please input a expression :"<<endl;
	//cin>>original;        //cin认为一个空格是输入的结束,getline是可以输入空格的,以回车作为结束符
	getline(cin,original);  //用getline 而不用cin>>输入的好处是可以接受空格,例如9.3 / 3这样的输入是可以接受的,可以处理的。
	nipolish = Reverse(original);
	cout<<"The Reverse Polish Notation is:"<<endl<<nipolish<<endl;
	result = Calculate(nipolish);
	cout<<"The result of expression is "<<result<<endl;

	return 0;
}





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* 表达式计算 */ /* 调用方式:CalcExp('1+max(0.5,sin(1))+sum(1,2^3,mod(5,3))', res, infoStr) */ /* 带符号参数调用方法,先调用符号定义AddSignParam,再调用 CalcExp: */ /* AddSignParam(['a','s'], [1, 0.5]); 或者 AddSignParam('a=1,s=0.5') */ /* CalcExp('1+a+sin(s)', res, infoStr) */ /* 其中res存储计算结果,为double型;infoStr存储计算时的提示信息,为string */ 表达式计算器 V2.3 支持以下功能: 1、四则运算 + - * / 、括弧()、正负(+ -) 2、百分数 %、求幂 ^ 、整数阶乘 ! (1 至 150) 3、参数符号计算,示例:a+b @@a=1,b=2 结算结果为3 用@@表示表达式中定义符号的值 4、常数e、圆周率PI 5、丰富的函数功能: 统计函数: max,min,sum,avg,stddev 标准偏差,均支持多参数 三角函数: sin,cos,tan,arcsin,arccos,arctan degrad(60) 角度转弧度 raddeg(3.14) 弧度转角度 costh(a,b,c) 余弦定理 cosC) 指数对数函数:sqrt,power(x,y),abs,exp,log2,log10,logN(a,N),ln 数据处理函数:int(x),trunc(x) 取整 frac(x) 取小数部分 round(x) 四舍五入取整 roundto(x,-1) 保留一位小数 mod(M,N) 求模 几何面积函数:s_tria(a,b,c) 三角形面积 s_circ(r) 圆形面积 s_elli(a,b) 椭圆面积 s_rect(a,b) 矩形面积 s_poly(a,n) 正多边形面积 平面几何函数:pdisplanes(x1,y1,x2,y2) 平面两点距离 pdisspace(x1,y1,z1,x2,y2,z2) 空间两点 p_line(x0,y0, A, B, C) 平面点到线距离 p_planes(x0,y0,z0 A, B, C, D)空间点到面距离 数列求和: sn(a1, d, n) 等差数列前n项和 sqn(a1, q, n) 等比数列前n项和 个税计算函数:intax(x), arcintax(x) 个税反算 6 、历史计算记录,双击计算记录可重新修改计算 示例: sin(1)+(-2+(3-4))*20% , e^63+PI , 15! , log2(max(2,3)) 注: 运算符必须为半角格式,三角函为弧度,输入可用空格间隔
逆波兰表达式(Reverse Polish Notation,RPN)是一种将运算符放在操作数之后的数学表达法,可以用来表示算术表达式。在C语言中,我们可以使用栈来求解逆波兰表达式。 以下是一个用C语言实现逆波兰表达式求值的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_SIZE 100 // 栈的最大容量 // 定义栈结构 typedef struct { double data[MAX_SIZE]; int top; } Stack; // 初始化栈 void initStack(Stack *stack) { stack->top = -1; } // 判断栈是否为空 int isEmpty(Stack *stack) { return stack->top == -1; } // 判断栈是否已满 int isFull(Stack *stack) { return stack->top == MAX_SIZE - 1; } // 入栈 void push(Stack *stack, double value) { if (isFull(stack)) { printf("Stack is full\n"); exit(1); } stack->top++; stack->data[stack->top] = value; } // 出栈 double pop(Stack *stack) { if (isEmpty(stack)) { printf("Stack is empty\n"); exit(1); } double value = stack->data[stack->top]; stack->top--; return value; } // 逆波兰表达式求值 double evaluateRPN(char *expression) { Stack stack; initStack(&stack); char *token = expression; double operand1, operand2, result; while (*token != '\0') { if (isdigit(*token)) { push(&stack, atof(token)); } else if (*token == '+' || *token == '-' || *token == '*' || *token == '/') { operand2 = pop(&stack); operand1 = pop(&stack); switch (*token) { case '+': result = operand1 + operand2; break; case '-': result = operand1 - operand2; break; case '*': result = operand1 * operand2; break; case '/': result = operand1 / operand2; break; } push(&stack, result); } // 移动到下一个token while (*token != ' ' && *token != '\0') { token++; } if (*token == ' ') { token++; } } return pop(&stack); } int main() { char expression[] = "5 3 4 * + 2 /"; double result = evaluateRPN(expression); printf("Result: %.2f\n", result); return 0; } ``` 以上代码可以通过使用栈来实现逆波兰表达式的求值。在主函数中,我们定义了一个逆波兰表达式"5 3 4 * + 2 /",并调用`evaluateRPN`函数来求解该表达式的值。最终输出结果为"Result: 7.50"。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值