C++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换

一、前言

    通常我们把栈归为一种基本的数据结构,同时它也是一种线性表结构,也就是说你要自己实现一个栈的数据结构,既可以用数组实现,也可以用链表实现。栈最主要的特点就是“先进后出”,因为栈只有一个入口和出口。

 

二、实现栈结构

    根据栈的先进后出的特点,很容易设置栈结构的接口:入栈、出栈、判空、size()等,熟悉数据库的同学都知道数据库无非就是四种操作:增、删、改、查,其实对于一个数据结构的接口而言,也是这四种操作,就栈而言,入栈即增操作、出栈即删操作、由于栈是线性表结构,所以查和改操作都需要遍历整个栈结构。现在已经知道了栈的接口操作,我们就可以用线性表的方法来实现一个栈结构,其实也就两种,用链表或数组实现栈。

    但是,在C++标准库中已经为我们实现了栈结构,而且是按照最高效率、最优的标准实现的,你可以放心的使用C++标准库提供的栈结构,以C++一贯的作风,其实现的栈结构是一个栈类型,定义在<stack>头文件中,使用的时候只需要#include该头文件就行。

    根据C++STL的解释,或C++Primer(第五版P329)的解释,都把stack类型称为一个容器适配器(配接器),并没有称其为一个容器,尽管如此,你可以把stack看作是一个特殊的容器,所谓适配器(配接器),指的是一种机制,一个容器适配器使一个容器的行为看起来像另外一个容器,这句话说的是什么意思呢?这是因为C++的容器适配器都是基于基本容器实现的,比如stack就是基于queue实现的(默认,也可以自己显视的指定为vector),这也导致了任何stack的操作接口都是直接调用底层容器的操作来完成的,如stack的push操作(入栈)就是调用queue的push_back操作来完成的。下面给出STL中stack的定义文件:

 

//模板定义
template<class _Ty,class _Container = deque<_Ty> >
class stack
{
protected:
	_Container c;	//底层容器对象,_Container是指底层容器类型

public:
	typedef stack<_Ty, _Container> _Myt;     //类型别名定义
	typedef _Container container_type;
	typedef typename _Container::value_type value_type;
	typedef typename _Container::size_type size_type;
	typedef typename _Container::reference reference;
	typedef typename _Container::const_reference const_reference;

	stack(): c()
	{
		//默认构造函数,构造空栈,这里是调用其成员容器对象的默认构造函数
	}

	stack(const _Myt& _Right): c(_Right.c)
	{	
		// construct by copying _Right
	}

	explicit stack(const _Container& _Cont): c(_Cont)
	{	
		// construct by copying specified container
	}

	void push(value_type&& _Val)
	{	
		//直接调用底层容器的操作实现stack自身接口
		// insert element at beginning
		c.push_back(_STD move(_Val));
	}

	bool empty() const
	{	
		// test if stack is empty
		return (c.empty());
	}

	size_type size() const
	{	
		// test length of stack
		return (c.size());
	}

	reference top()
	{	
		// return last element of mutable stack
		return (c.back());
	}

	const_reference top() const
	{	
		// return last element of nonmutable stack
		return (c.back());
	}

	void push(const value_type& _Val)
	{	
		// insert element at end
		c.push_back(_Val);
	}

	void pop()
	{	
		// erase last element
		c.pop_back();
	}
};

关于上面的C++STL中stack的定义,你可以不了解,你只需要知道stack提供给你哪些接口,这些接口应该怎么用就行了,至于其内部实现,STL已经为你实现好了,完全不用你担心。

 

 

 

三、栈的应用

1、后缀表达式求值

 

    以人类的思维,中缀表达式是正常的表达式形式,因为我们已经熟悉了各种运算符号的优先级,知道在一个表达式中第一个求哪一部分的值,最常见的就是先求括号内部,然后再求括号外部,但是这种求值顺序在计算机看来是很麻烦的,最好的办法是我们输入给计算机的表达式不需要知道操作符优先级,计算机只管按照我们输入的表达式从左到右求值即可,这就要用后缀表达式来实现。后缀表达式是针对中缀表达式而言的,大致可以理解为操作符在两个操作数之后,并不是像中缀表达式那样每两个操作数之间必须有一个操作符,后缀表达式最大的特点就是没有必要知道任何运算符的优先规则,如下就是一个后缀表达式:

“4.99 1.06 * 5.99 + 6.99 1.06 * + ”

其中缀表达式为:“4.99 * 1.06 + 5.99 + 6.99 * 1.06 ”(关于怎么从中缀表达式转为后缀表达式后面会介绍)

    后缀表达式的求值规则为:从左到右扫描后缀表达式,如果遇到一个操作数,将其压入栈中,如果遇到一个操作符,则从栈中弹出两个操作数,计算结果,然后把结果入栈,直到遍历完后缀表达式,则计算完成,此时的栈顶元素即为计算结果,如上的后缀表达式求值过程为:

初始,栈空;步骤(1)

遇到操作数4.99,入栈;步骤(2)

遇到操作数1.06,入栈;步骤(3)

遇到操作符*,弹出栈中两个元素,计算结果入栈;步骤(4)

遇到操作数5.99,入栈;步骤(5)

遇到操作符+,弹出栈中两个元素,计算结果入栈;步骤(6)

遇到操作数6.99,入栈;步骤(7)

遇到操作数1.06,入栈;步骤(8)

遇到操作符*,弹出栈中两个元素,计算结果入栈;步骤(9)

遇到操作符+,弹出栈中两个元素,计算结果入栈;步骤(10)

C++实现代码如下(注意输入的后缀表达式每个元素之后一定要有一个空格,这用于分开不同的元素):

 

/*********************后缀表达式求值(直接利用C++STL提供的Stack实现)**************************/
double postfixExpression(const string &str)
{
	stack<double> mystack;    //栈空间

	string s = ".0123456789+-*/";
	string empty = " ";
	string numbers = ".0123456789";
	string c = "+-*/";

	double firstnum;
	double secondnum;
	double sum;

	for(unsigned int i=0; i<str.size(); )
	{
		string::size_type start = str.find_first_of(s,i);     //查找第一个数字或算术符号
		string::size_type end = str.find_first_of(empty,i);   //查找第一个空格
		string tempstr = str.substr(start, end-start);     //取出这一个元素

		//判断元素
		if(tempstr == "+" || tempstr == "-" || tempstr == "*" || tempstr == "/")
		{
			secondnum = mystack.top();    //取当前栈顶元素,由于栈的先进后出特性,当前栈顶元素其实是二元操作符中右侧的操作数,如表达式3-2的后缀表达式为“3 2 -”,这里secondnum取得数就是2
			mystack.pop();
			firstnum = mystack.top();
			mystack.pop();
			if(tempstr == "+")
			{
				sum = firstnum + secondnum;
				mystack.push(sum);
			}
			if(tempstr == "-")
			{
				sum = firstnum - secondnum;
				mystack.push(sum);
			}
			if(tempstr == "*")
			{
				sum = firstnum * secondnum;
				mystack.push(sum);
			}
			if(tempstr == "/")
			{
				sum = firstnum / secondnum;
				mystack.push(sum);
			}
		}
		else
		{
			double num = stod(tempstr);
			mystack.push(num);
		}

		//控制迭代
		i = end + 1;
	}
	return mystack.top();
}

代码测试如下:还是以上面那个例子的后缀表达式作为输入,则测试代码如下:

 

 

void main()
{
	string Postfistr = "4.99 1.06 * 5.99 + 6.99 1.06 * + ";    //每个元素后需要有一个空格“ ”字符串
	double res = postfixExpression(Postfistr);
	cout << res <<endl;
}

 

    上述计算后缀表达式的前提是输入的表达式就是后缀表达式,但是一般我们给出的表达式为中缀表达式,这就需要先把中缀表达式转为后缀表达式。

 

2、中缀表达式转为后缀表达式

    中缀表达式转为后缀表达式也有一定的规则,这个规则是根据操作符的运算优先级来定的,还是上面那个中缀表达式为:“4.99*1.06+5.99+6.99*1.06”,转为后缀表达式的规则为:

    (1)这里定义一个操作符栈stack来保存遇到的操作符,还需要定义string作为后缀表达式输出返回;

    (2)首先需要对输入的中缀表达式进行“切片”处理,所谓切片,即对所输入的中缀表达式进行元素分割,这里的每个元素要么是一个操作符(“+-*/()”),要么是一个操作数(“.0123456789”),把这些元素存储到一个vector<string>Inputvec中;

    (3)然后依次遍历Inputvec中元素,根据其是操作数还是操作符来进行不同的“处理”。这里的处理规则为:

            如果是操作数,则直接保存到输出string中;

            如果是操作符

                如果操作符栈为空,则把操作符入栈;
                否则,则比较当前运算符与栈顶操作符优先等级;

                    如果当前操作符优先等级高,则当前操作符入栈;

                    否则,弹出栈顶操作符到输出string中;

中缀表达式转后缀表达式C++实现代码如下:

 

//设置操作符优先级,这里考虑到括号("("、")")匹配,定义设置左括号"("的优先级最高,且只有在遇到右括号时才弹出左括号
int priority(const string str)  
{
	const char *op = str.c_str();
    switch(*op)  
    {
    case ')':
        return 0;  
    case '+':  
    case '-':  
        return 1;  
    case '*':  
    case '/':  
        return 2; 
	case '(':
		return 3;
    default :  
        return -1;  
    }  
}  

/*********************中缀表达式转为后缀表达式**************************/
string InfixToPostfi(const string &str)
{
	string operatorstr = "*-/+()";      //用于string搜索
	string numbers = "0123456789.";

	//对输入的中缀表达式中每个元素进行切片,每个元素存储到vector<string>Inputstr
	vector<string> Inputvec;   //存储切片结果
	for(unsigned int i=0; i<str.size(); )
	{
		string::size_type operatorindex = str.find_first_of(operatorstr,i);     //搜索str中从i开始的第一个操作符
		if(operatorindex != string::npos)
		{
			//如果从i开始搜索到了操作符
			if(operatorindex == i)
			{
				//如果是两个连续的操作符,即这种形式的表达式   a*(b+c)+d;
				string tempstr = str.substr(operatorindex,1);
				Inputvec.push_back(tempstr);
				i = i+1;
			}
			else
			{
				Inputvec.push_back(str.substr(i,operatorindex-i));
				Inputvec.push_back(str.substr(operatorindex,1));
				i = operatorindex+1;
			}
		}
		else
		{
			//如果从i开始搜索到了操作符,即输入的中缀表达式以操作数结尾,不是以操作符结尾(其实一个表达式以操作符结尾的情况只可能是以右括号")"结尾,这里就是为防止这种特殊情况)
			Inputvec.push_back(str.substr(i,str.size()-i));
			i = str.size();
		}
	}

	//遍历切片结果vector中每个元素
	stack<string> operatorstack;     //创建空栈,用来存储操作符
	vector<string> PostfiOutvec;     //存储中缀输出,这里是存储到vector
	for(int i=0; i<Inputvec.size(); i++)
	{
		//如果当前元素是操作符
		if(Inputvec[i].find_first_of(operatorstr) != string::npos)
		{
			if(operatorstack.empty())     
			{
				operatorstack.push(Inputvec[i]);      //如果操作符栈空,则直接入栈
			}
			else
			{
				if(Inputvec[i] == ")")     //如果当前操作符是右括号
				{
					while(operatorstack.top() != "(")
					{
						PostfiOutvec.push_back(operatorstack.top());     //将栈顶操作符输出
						operatorstack.pop();    //删除栈顶元素
					}
					operatorstack.pop();    //删除栈顶元素(这里是删除左括号"(")
				}
				else
				{
					int curpri = priority(Inputvec[i]);     //获取操作符的优先级

					//比较当前操作符与栈顶元素优先级,如果小于或等于栈顶元素优先级则弹出栈顶元素,否则当前操作符入栈
					while(!operatorstack.empty())
					{
						string top = operatorstack.top();     //返回栈顶元素
						int toppor = priority(top);     //栈顶元素优先级

						if((curpri <= toppor) && top!="(")       //左括号优先级最大,但是它只有遇到右括号才输出
						{
							PostfiOutvec.push_back(top);
							operatorstack.pop();    //删除栈顶元素
						}
						else
							break;
					}
					operatorstack.push(Inputvec[i]);
				}
			}
		}
		//如果当前元素是操作数,直接输出
		else
		{
			PostfiOutvec.push_back(Inputvec[i]);
		}
	}
	while(!operatorstack.empty())
	{
		PostfiOutvec.push_back(operatorstack.top());      //输出操作符栈中的其他操作符
		operatorstack.pop();
	}

	//在输出中插入空格
	vector<string>::const_iterator itr=PostfiOutvec.begin()+1;
	while(itr!=PostfiOutvec.end())
	{
		itr = PostfiOutvec.insert(itr," ");      //这里一定要返回insert之后的指针,因为改变容器的操作会使迭代器失效
		itr+=2;
	}
	
	PostfiOutvec.push_back(" ");     //添加最后一个空格

	//vector输出为string,作为后缀表达式结果返回
	string result;
	for(int i=0; i<PostfiOutvec.size(); i++)
	{
		result.append(PostfiOutvec[i]);
	}

	return result;
}

测试代码如下:

 

 

void main()
{
	string Infixstr1 = "4.99*1.06+5.99+6.99*1.06";      //没有括号
	string Infixstr2 = "4.99*1.06+5.99+(6.99*1.06)";    //中缀表达式以操作符结尾(这种情况只能是以右括号结尾)
	string Infixstr3 = "4.99*(1.06+5.99)+6.99*1.06";    //括号在中间
	string Infixstr4 = "4.99*1.06+5.99+()6.99*1.06";    //插入括号,其内没有表达式
	string Postfistr1 = InfixToPostfi(Infixstr1);
	string Postfistr2 = InfixToPostfi(Infixstr2);
	string Postfistr3 = InfixToPostfi(Infixstr3);
	string Postfistr4 = InfixToPostfi(Infixstr4);
	double res1 = postfixExpression(Postfistr1);
	cout << "res1=" << res1 <<endl;
	double res2 = postfixExpression(Postfistr2);
	cout << "res2=" << res2 <<endl;
	double res3 = postfixExpression(Postfistr3);
	cout << "res3=" << res3 <<endl;
	double res4 = postfixExpression(Postfistr4);
	cout << "res4=" << res4 <<endl;
}

以上测试代码中,测试了4中不同的中缀表达式形式,运行结果如下:

 



 

 

 

 

  • 29
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值