编译原理 | 实验四 | 逆波兰式

目录

 

一.问题分析

二.算法思想

1.关于分词器

2.关于逆波兰式分析器:

三.实现代码

1.头文件  &  类视图

2.预处理部分

 3.逆波兰分析过程

4.计算,输出部分

四.总结 


一.问题分析

将用中缀式表示的算术表达式转换为用逆波兰式(后缀式)表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值.

如输入如下:     21+(( 42-2) *15+6  ) -18#

输出为:

原来表达式:      21+ (( 42-2 ) *15+6 ) - 18#

后缀表达式:      21&42&2&-15&*6&++18&-

计算结果:  609

 

 

二.算法思想

1.关于分词器

    将输入串的所有非空白元素添加到一个vector <string> 数组里

    在输入串(string)里设置两个(广义上的)指针,一前一后指向非空白元素.遇到空白字符自动向后跳

前   \       后

数字

字符

数字

temp+=input[cur]

temp+= input[cur]

token.push_back(temp)

temp.clear()

字符

temp+=input[cur]

token.push_back(temp)

temp.clear()

比如(5+…)

假设++,--操作是不存在的

Ep:碰到(3+5)*3

temp+=input[cur]

token.push_back(curString)

cur.clear()

 

 

2.关于逆波兰式分析器:

或阅读该文:逆波兰分析

 

三.实现代码

1.头文件  &  类视图

using namespace std;
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<algorithm>

/*
逆波兰式不需要括号,当从中缀式转换为RPN时,遇到 ( 括号要暂存之后的运算符;遇到 ) 括号要将内部的运算符(在TempStack)挨个转移到finStack

逆波兰计算的是表达式,可以写一个简化的分词器函数(只分析数字,运算符,空格),
将各个长度不一(如"55"长度为2,"+"长度为1)的元素分别进入一个  vector<string> token;
这个函数无需分析词性.分析的工作在核心函数Analysis()中对token[i][0]判别完成,
Analysis()同时还要调用isSym()来进行判别工作

tempStack  总是将优先级高的运算符放在栈顶,压住优先级低的运算符.当转移到finStack时,优先级高的先转移
*/


class RPN
{
public:
	RPN(string);
	~RPN();
	bool isLegal();					//检测输入串是否含有非法字符(不是运算符||操作数的字符)
	bool isNum(char);				//经过分词程序后形成的vector<string> token. input:token[i][0] 每个元素的第一个字符
	void divideElement();			//分词器
	int Prio(char);					//判断运算符的优先级,数字越小,优先级越弱
	void analysis();					//分析程序,将token->逆波兰式
	void Print();						//打印整个逆波兰式
	void Computing();				//根据逆波兰式计算解
	float getRes();					//返回最终值
private:
	stack<string>tempExp;		//临时栈,记录临时存放的运算符
	stack<string>finExp;		//最终栈,操作数依序放入即可
	vector<string>Result;	//记录fin栈的结果

	vector<string>token;	//接收分词后的字符串
	string input;					//接收输入的字符串
	float res;						//接收计算逆波兰式的最终结果

};

 

2.预处理部分

    包括判断是否数字,输入是否合法,将输入串分词为独立的元素,存入token 数组

/*
	isLegal():		检测是否存在非法字符,即除了数字和运算符之外的其他所有字符
	input:			类数据成员 vector<string> Input
	out:				存在非法字符,报错.  (调用isLegal的函数会据此决定是否返回)
*/
bool RPN::isLegal()
{
	bool flag = true;


	for (size_t i = 0; i < input.size(); i++)		//遍历输入串的每一个char
	{
		if ((input[i] >= '0') && (input[i] <= '9'))				//判断数字
		{
			continue;
		}
		else															//判断运算符
		{
			if (input[i] != '+' || input[i] != '-' || input[i] != '*' || input[i] != '/' ||
				input[i] != '(' || input[i] != ')' || input[i] != '#' || input[i] != ' ')
			{
				flag = false;
			}
		}
	}
	return flag;;
}

/*
	isSym():		经过isLegal() 处理后,只剩下数字,运算符,空格
	return true:	是运算符
	return false:	是数字
*/
bool RPN::isNum(char ch)
{
	if (ch>='0' &&ch<='9')
	{
		return true;
	}
	else
	{
		return false;
	}
}

/*
	divideElement():	将输入串分为一个个不含空格的元素,存储在一个vector<string>里
	input:					成员变量:input 
	output:					成员变量:  vector<string> token

*/
void RPN::divideElement()
{
	
	size_t next = 1;		//一开始,指向input[1]
	string temp;			//临时存放字符

	for (size_t cur = 0; cur < input.size(); cur++,next++)		//cur从[0]->[n-2]
	{
		if (next == input.size() - 1) { next--; }//当next到达最后一个,cur到达倒数第二个时.让next回退 1,可以有效阻止range溢出

		if (input[cur] == ' ' || input[cur] == '\t')    //跳过空格
		{
			cur++;
			next = cur + 1;		//next总指向cur下一个
		}
		if (input[next] == ' '|| input[next] == ' \t')
		{
			next++;
		}
	
		if (isNum(input[cur]))//第一个是数字
		{
			if (isNum(input[next]))  //第二个是数字
			{
				temp += input[cur];			//将前一位添加到temp

				if (cur == input.size() - 1)	//当到达末尾时
				{
					token.push_back(temp);
					temp.clear();
				}
			}
			else								//第二个是字符
			{
				temp += input[cur];				//前一个进入temp
				token.push_back(temp);		//token添加这个temp
				temp.clear();							//清空temp,待留下次使用
			}
		}
		else
		{
			temp += input[cur];
			token.push_back(temp);
			temp.clear();
		}
	}
}

 

 3.逆波兰分析过程

    包括优先级判断函数,以及核心的逆波兰分析程序

/*
	Prio():	对每个字符进行判别
	input:	token[i][0],即token每个元素的第一个字符
	return:	该字符的优先级
*/
int RPN::Prio(char ch)
{
	switch (ch)
	{
	case '#':return 0;

	case'+':
	case'-':return 1;

	case'*':
	case'/':
	case'×':return 2;

	case'(':
	case')':return 3;


	default:
		break;
	}
	return 0;
}

/*
	analysis():	程序的核心部分,进行逆波兰式的转换
	input():		token向量,temp栈,fin栈
	return:		finStack
*/
void RPN::analysis()
{
	tempExp.push("#");			//先将优先级最低的"#"放在栈底

	for (size_t i = 0; i < token.size(); i++)  //遍历token
	{
		if (isNum(token[i][0]))				//要么是数字
		{
			finExp.push(token[i]);
		}
		else										//要么是运算符
		{
			if (token[i] == "(")					//若x是'(',则直接压入temp
			{
				tempExp.push(token[i]);
			}
			else if (token[i] == ")")		//若x是')',则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次压入栈fin,
			{
				while (tempExp.top() != "(")
				{
					finExp.push(tempExp.top());
					tempExp.pop();
				}
				tempExp.pop();				//此时抛弃 "("
			}
			else									//此时不是( 与 )
			{
				if (tempExp.top()=="(")			//此时temp栈顶是(,直接将扫描字符压入temp,因为()内是优先级最高的
				{
					tempExp.push(token[i]);
				}
				else										//栈顶元素不为'('
				{
					if (Prio(token[i][0])>Prio(tempExp.top()[0]))			//如果 token[i]的优先级 > temp栈顶的优先级,直接压入
					{
						tempExp.push(token[i]);
					}
					else
					{
						while (Prio(token[i][0]) <= Prio(tempExp.top()[0]) && tempExp.top()!="(")    //否则,不断地将temp的栈顶转移到fin(不要括号),直到(  temp的栈顶 的优先级 小于 token[i] 的优先级    或     栈顶为"("
						{
							finExp.push(tempExp.top());
							tempExp.pop();
						}
						tempExp.push(token[i]);
					}
				}
			}
		}
	}

	//检查tempExp是否为空,否则依次转移到finExp
	if (!tempExp.empty())
	{
		while (tempExp.top()!="#")
		{
			finExp.push(tempExp.top());
			tempExp.pop();
		}
	}
}

 

 

4.计算,输出部分

    包括将栈中的元素取出到一个vector,逆波兰式计算值

/*
	Print:		打印结果
	input:	finExp栈
	return:	一串cout结果
*/
void RPN::Print()
{
	//倒腾数据结构
	while (!finExp.empty())
	{
		Result.push_back(finExp.top());
		finExp.pop();
	}

	//想要逆转过来
	reverse(Result.begin(), Result.end());
	//逆序打印
	for (size_t i = 0; i <Result.size(); i++)
	{
		cout << Result[i] << "	";
	}
	cout << endl;
}

void RPN::Computing()
{
	while (Result.size()!=1)				//这样写比较耗费性能,容器物理上的增删需要花费额外的时间与内存.但是使用指针式的写法需要更多时间与精力,遂作罢
	{
		for (size_t i = 0; i < Result.size(); i++)
		{
			if (!isNum(Result[i][0]))							//需要找到第一个运算符
			{
				float temp=0.0;
				switch (Result[i][0])
				{
				case '+':
					temp = stof(Result[i - 2]) + stof(Result[i - 1]);
																	 //新值放在原运算符的位置上
			
					break;
				case '-':
					temp = stof(Result[i - 2]) - stof(Result[i - 1]);break;
				case '/':
					temp = stof(Result[i - 2]) / stof(Result[i - 1]);break;
				case'*':
				case'×':
					temp = stof(Result[i - 2]) * stof(Result[i - 1]);
					break;
				default:
					break;
				}
				Result[i] = to_string(temp);												 //新值放在原运算符的位置上
				Result.erase(Result.begin() + i - 2);										//删除前两个操作数
				Result.erase(Result.begin() + i - 2);
				i = 0;																					//每次执行完后重新从首元素开始
			}
		}
	}

	res=stof(Result[0]);
}

float RPN::getRes()
{
	return this->res;
}

 

5.mian()部分

int main()
{
	string str="21+(( 42-2)*15+6	) -18";
	cout << "表达式:  21+(( 42-2)*15+6	) -18\n";
	RPN rpn(str);
	//输入串判别
	if (!rpn.isLegal())
	{
		cout << "含有非法字符,请重新输入\n";
		//此处可以有一个goto语句
	}
	//分词
	rpn.divideElement();
	//分析
	rpn.analysis();
	//打印结果
	rpn.Print();
	//计算值
	rpn.Computing();
	cout<<rpn.getRes();

	system("pause");
	return 0;
}

 

 

四.总结 

其实最后的计算值部分应该用传址的方式写的,但那样需要更多的时间与精力,时间上不允许优化效率了.

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章