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

一、前言

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

 

二、实现栈结构

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

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

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

 

//模板定义
template >
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++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换_第1张图片

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

 

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

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

	double firstnum;
	double secondnum;
	double sum;

	for(unsigned int i=0; i

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

 

 

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

C++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换_第2张图片

 

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

 

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

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

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

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

    (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.";

	//对输入的中缀表达式中每个元素进行切片,每个元素存储到vectorInputstr
	vector Inputvec;   //存储切片结果
	for(unsigned int i=0; i operatorstack;     //创建空栈,用来存储操作符
	vector PostfiOutvec;     //存储中缀输出,这里是存储到vector
	for(int i=0; i::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

测试代码如下:

 

 

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 <C++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换_第3张图片



 

 

 

 

你可能感兴趣的:(数据结构与算法)