顺序栈计算器 中缀转后缀表达式


前言

参考书目:《数据结构(用面向对象方法与c++语言描述)第2版》,殷人昆主编
书中虽有介绍计算后缀表达式的方法和中缀转后缀的方法,但这两个函数却是分离的,并未介绍输入一个中缀表达式,由类内部自动转化为后缀表达式并运算的方法。本篇简略介绍后缀表达式运算及实现上述流程。


一、后缀表达式简述

后缀表达式是运算数在前,运算符在其后的一种表达式形式,如:

中缀表达式:1+4*(3-2)-6/7
后缀表达式:1 4 3 2 - * + 6 7 / -

后缀表达式运算方法:从左到右扫描,遇到运算符则把左侧最接近该运算符的两个数按该符号运算,用运算的结果替换掉这两个数的位置且把用过的运算符删掉,然后继续扫描。扫描完后,栈中剩一个数,该数即为运算式结果。

运算的过程图解:
在这里插入图片描述
容易发现运算取左侧最接近运算符的两个数字,或者说扫描到的最后两个数字这个操作正好吻合栈中数据的存取过程,因此用栈计算后缀表达式是比较方便的。

关于后缀表达式,这里做简要概括:

为什么要有优先数:我们知道像中缀表达式计算都有个先后顺序,像没有括号的情况下先算乘除后算加减,这其实就是优先数的体现。观察上面的后缀表达式,再结合后缀表达式的运算方法,可以发现后缀表达式的运算顺序与运算符在式中的位置有关,运算次序为从左到右,那么在中缀转后缀表达式的过程中,如何正确排好运算符的顺序便是一个关键问题,设置优先数便是帮助我们将运算符正确排序的一种方法。

后缀表达式中的各符号优先级:

操作符#(*,/,%+,-)
isp01536
icp06421

注:‘#’是表达式的终止符
isp:栈内优先数
icp:栈外优先数

方法:
栈中初始压入一个#,然后对中缀表达式从左到右检索,直到遇到#。遇到数字直接输出(或并入储存后缀表达式的字符串,然后加一个空格),若遇到操作符:
①当前操作符栈外优先数大于栈顶操作符栈内优先数,当前操作符入栈;
②若小于,栈顶操作符退栈并输出(或并入储存后缀表达式的字符串,然后加一个空格);
③若等于,栈顶操作符退栈但不输出,如果退‘(’,读入下一字符。算法结束得到后缀表达式。

优先数的设置有如下特点:
①无论栈内栈外,乘、除、取余符号的优先数都大于加、减符号;
②优先数相等的只有这几种情况:
“#”遇到“#”,此时栈中只剩最初入栈的“#”,算法结束;
栈内“(”遇到栈外“)”,“)”是不入栈的,并且将使相匹配的“(”出栈,因为icp[‘)’]的优先级极小,在“(”出栈前这两个半括号间的运算符将全部出栈,体现优先运算括号内容的规则;
栈内“)”遇到栈外“(”,这种情况并不会发生,因为icp[‘)’]的优先级极小,只有“#”的更小,但“#”是栈中第一个元素或表达式终止符,“)”入栈说明表达式有问题。
③“(”入栈后优先数变极低,方便后续运算符入栈。

二、参考书目中的函数实现

1.输入一个后缀表达式并计算

代码如下:

//代码来源:《数据结构(用面向对象方法与c++语言描述)第2版》,殷人昆主编
void Calculator::Run()
{
	char ch;
	double newOperand;
	while (cin >> ch, ch != '#')
	{
		switch (ch)
		{
		case '+':case '-':case '*': case '/':
			DoOperator(ch);//数据栈中退出两个数,执行相应运算,运算结果再压入栈中
			break;
		default:
			cin.putback(ch);
			cin >> newOperand;
			AddOperand(newOperand);//将新数据压入数据栈中
		}
	}
}

2.将中缀转后缀表达式

代码如下(示例):

//代码来源:《数据结构(用面向对象方法与c++语言描述)第2版》,殷人昆主编
void postfix(express e)
{
	Stack<char> s;
	char ch = '#', ch1, op;
	s.Push(ch);
	cin.get(ch);
	while (s.IsEmpty() == false && ch != '#')
	{
		if (isdigit(ch))
		{
			cout << ch;
			cin.get(ch);
		}
		else
		{
			s.getTop(ch1);
			if (isp(ch1) < icp(ch)) 
			{
				s.Push(ch);
				cin.get(ch);
			}
			else if (isp(ch1) > icp(ch))
			{
				s.Pop(op);
				cout << op;
			}
			else
			{
				s.Pop(op);
				if (op == '(')
					cin.get(ch);
			}
		}
	}
}

虽然这两个方法分别实现了对应功能,但函数Run中前提是用户输入一个后缀表达式,函数postfix却是将用户输入的中缀表达式转为后缀表达式后输出,两者之间没有出现联动,无法实现计算效果。接下来,将介绍如何将两个方法结合。


三.在原方法基础上改写并结合两个方法

1.输入一个后缀表达式并计算

void Calculator::Run()//计算后缀表达式
{
	string expression;
	while (cin >> expression, expression != "#")
	{
		expression += '#';
		expression = postfix(expression);//表达式由中缀表达式变为后缀表达式
		char ch;
		double newOperand;
		for (int i = 0, ch = expression[0];ch != '#'&&i < expression.length();i++, ch = expression[i])
		{
			if (ch == ' ')
				continue;
			switch (ch)
			{
			case'+':case'-':case'*':case'/'://若为运算符
				DoOperator(ch);
				break;
			default://若为数字
				int end = i + 1;
				for (;(isdigit(expression[end]) || expression[end] == '.') && expression[end] != '#';end++);//检索到数字的最后一位的后一位
				newOperand = stod(expression.substr(i, end-i));
				i = end;//已经读过的内容不必再读,更新游标
				AddOperand(newOperand);
			}
		}
		double result;
		s.getTop(result);
		cout << "运算结果:" << result << endl;
		Clear();
	}
}

分析:有键盘输入一个中缀表达式字符串,可以循环计算(到输入’#'时退出),字符串传入postfix中经过处理转换为中缀表达式。原代码中主要是对输入流中数据进行操作,这里直接对字符串进行操作。
遍历字符串,若为运算符则入栈;若为数字,设立终点end,end往后检索直到非数字或非‘.’(为了可以进行浮点数运算),[i,end)的字串则为当前需要入栈的数字,使用substr截取再用stod转浮点数入栈,然后更新i。

2.将中缀转后缀表达式

string Calculator::postfix(string expression)//中缀转后缀表达式
{
	SeqStack<char>s1;
	char ch, ch1, op;
	string str = "";
	s1.Push('#');
	for (int i = 0;!s1.IsEmpty() && i < expression.length();)
	{
		ch = expression[i];
		if (isdigit(ch) || ch == '.')
		{
			while (isdigit(ch) || ch == '.')
			{
				str += ch;
				i++;
				ch = expression[i];
			}
			str += ' ';
		}
		else
		{
			s1.getTop(ch1);
			if (isp[ch1] < icp[ch])//新操作符优先级高,入栈,游标右移
			{
				s1.Push(ch);
				i++;
			}
			else if (isp[ch1] > icp[ch])//新操作符优先级低,出栈,游标不变
			{
				s1.Pop(op);
				str += op;
				str += ' ';
			}
			else//栈内外优先数相等的只有与'('和')'
			{
				s1.Pop(op);
				if (op == '(')
					i++;
			}
		}
	}
	str += '#';
	return str;
}

分析:为传入的中缀字符串加上’#'作为终止符,操作符出栈入栈判定和原方法相同,不同的是原方法中输出语句改为拼接到字符串str中,并加个空格。最终得到的str即是后缀表达式,返回。

四.运行测试

在这里插入图片描述

五.优点和不足

可以进行浮点数的运算,快速计算较长的中缀表达式,但目前还未完善负数的运行。

六.简易计算器类完整代码

//顺序栈,中缀转后缀并计算后缀表达式,可处理小数但不能处理负数
#include<iostream>
#include<assert.h>
#include<math.h>
#include<string>
#include<map>
using namespace std;

//栈的类定义
const int maxSize = 50;
template<class T>
class Stack
{
public:
	Stack() {};
	virtual void Push(const T& x) = 0;
	virtual bool Pop(T& x) = 0;
	virtual bool getTop(T& x)const = 0;
	virtual bool IsEmpty()const = 0;
	virtual bool IsFull()const = 0;
	virtual int getSize()const = 0;//计算栈中元素个数
};

//顺序栈的类定义
const int stackIncreament = 20;//栈溢出时扩展空间的增量
template<class T>
class SeqStack :public Stack<T>
{
private:
	T *elements;//存放栈中元素的栈数组
	int top;
	int maxSize;
	void overflowProcess();
public:
	SeqStack(int sz = 50);
	~SeqStack() { delete[] elements; };
	void Push(const T& x);
	bool Pop(T& x);
	bool getTop(T& x)const;
	bool IsEmpty()const { return (top == -1) ? true : false; }
	bool IsFull()const { return (top == maxSize - 1) ? true : false; }
	int getSize()const { return top + 1; }
	void MakeEmpty() { top = -1; }
	friend ostream& operator<<(ostream& os, SeqStack<T>& s);
};
template<class T>
SeqStack<T>::SeqStack(int sz) :top(-1), maxSize(sz)
{
	elements = new T[maxSize];
	assert(elements != NULL);
}
template<class T>
void SeqStack<T>::overflowProcess()
{
	T *newArray = new T[maxSize + stackIncreament];
	if (newArray == NULL)
	{
		cerr << "存储分配失败!" << endl;
		exit(1);
	}
	for (int i = 0; i <= top; i++)
		newArray[i] = elements[i];
	maxSize = maxSize + stackIncreament;
	delete[]elements;
	elements = newArray;
}
template<class T>
void SeqStack<T>::Push(const T& x)
{
	if (IsFull())
		overflowProcess();
	elements[++top] = x;
}
template<class T>
bool SeqStack<T>::Pop(T& x)
{
	if (IsEmpty())
		return false;
	x = elements[top--];
	return true;
}
template<class T>
bool SeqStack<T>::getTop(T& x)const
{
	if (IsEmpty())
		return false;
	x = elements[top];
	return true;
}
template<class T>
ostream& operator<<(ostream& os, SeqStack<T>& s)
{
	os << "top=" << s.top << endl;
	for (int i = 0;i <= s.top;i++)
		os << i << ":" << s.elements[i] << endl;
	return os;
}


class Calculator//利用顺序栈构建一个计算器,采用中缀转后缀表达式方法计算
{
public:
	Calculator(int sz) :s(sz)
	{
		isp['#'] = 0;
		isp['('] = 1;
		isp['*'] = 5;
		isp['/'] = 5;
		isp['+'] = 3;
		isp['-'] = 3;
		isp[')'] = 6;
		icp['#'] = 0;
		icp['('] = 6;
		icp['*'] = 4;
		icp['/'] = 4;
		icp['+'] = 2;
		icp['-'] = 2;
		icp[')'] = 1;
	}
	void Run();
	void Clear() { s.MakeEmpty(); }
private:
	map<char, int>isp, icp;//操作符的栈内、栈外优先级,用map表示
	SeqStack<double>s;
	void AddOperand(double value) { s.Push(value); }//进操作数栈
	bool getTwoOperands(double& left, double& right);//从栈中退出两个操作数
	void DoOperator(char op);//形成运算指令,进行运算
	string postfix(string expression);
};
bool Calculator::getTwoOperands(double& left, double& right)
{
	if (s.getSize() < 2)
	{
		cout << "栈内操作数少于两个!" << endl;
		return false;
	}
	s.Pop(right);//注意由于栈的特性,右操作数在左操作数上方
	s.Pop(left);
	return true;
}
void Calculator::DoOperator(char op)
{
	double left, right, value;
	bool successGet = getTwoOperands(left, right);
	if (successGet)
	{
		switch (op)//加减乘除操作
		{
		case'+':
			value = left + right;
			s.Push(value);
			break;
		case'-':
			value = left - right;
			s.Push(value);
			break;
		case'*':
			value = left * right;
			s.Push(value);
			break;
		case'/':
			if (right == 0.0)
			{
				cout << "除数为0!" << endl;
				Clear();
			}
			else
			{
				value = left / right;
				s.Push(value);
			}
			break;
		}
	}
	else
		Clear();
}
void Calculator::Run()//计算后缀表达式
{
	string expression;
	while (cin >> expression, expression != "#")
	{
		expression += '#';
		expression = postfix(expression);//表达式由中缀表达式变为后缀表达式
		char ch;
		double newOperand;
		for (int i = 0, ch = expression[0];ch != '#'&&i < expression.length();i++, ch = expression[i])
		{
			if (ch == ' ')
				continue;
			switch (ch)
			{
			case'+':case'-':case'*':case'/'://若为运算符
				DoOperator(ch);
				break;
			default://若为数字
				int end = i + 1;
				for (;(isdigit(expression[end]) || expression[end] == '.') && expression[end] != '#';end++);
				newOperand = stod(expression.substr(i, end-i));
				i = end;//已经读过的内容不必再读,更新游标
				AddOperand(newOperand);
			}
		}
		double result;
		s.getTop(result);
		cout << "运算结果:" << result << endl;
		Clear();
	}
}
string Calculator::postfix(string expression)//中缀转后缀表达式
{
	SeqStack<char>s1;
	char ch, ch1, op;
	string str = "";
	s1.Push('#');
	for (int i = 0;!s1.IsEmpty() && i < expression.length();)
	{
		ch = expression[i];
		if (isdigit(ch) || ch == '.')
		{
			while (isdigit(ch) || ch == '.')
			{
				str += ch;
				i++;
				ch = expression[i];
			}
			str += ' ';
		}
		else
		{
			s1.getTop(ch1);
			if (isp[ch1] < icp[ch])//新操作符优先级高,入栈,游标右移
			{
				s1.Push(ch);
				i++;
			}
			else if (isp[ch1] > icp[ch])//新操作符优先级低,出栈,游标不变
			{
				s1.Pop(op);
				str += op;
				str += ' ';
			}
			else//栈内外优先数相等的只有与'('和')'
			{
				s1.Pop(op);
				if (op == '(')
					i++;
			}
		}
	}
	str += '#';
	return str;
}
int main()
{
	cout << "请输入算数表达式,若想停止输入,请输入“#”。(请使用英文输入法,支持小数运算)" << endl;
	Calculator calculator(50);
	calculator.Run();
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值