使用堆栈计算后缀表达式

使用堆栈计算后缀表达式

一、实现栈结构

根据栈的先进后出的特点,很容易设置栈结构的接口:入栈、出栈、判空、size()等。可以用线性表的方法来实现一个栈结构,其实也就两种,用链表或数组实现栈。

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

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

template<class _Ty,class _Container = deque<_Ty> >
	class stack
	{	// LIFO queue implemented with a container
public:
	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;

	static_assert(is_same_v<_Ty, value_type>, "container adaptors require consistent types");

	stack() _NOEXCEPT_COND(is_nothrow_default_constructible_v<_Container>) //strengthened
		: c()
		{	// construct with empty container
		}

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

	template<class _Alloc,
		class = enable_if_t<uses_allocator_v<_Container, _Alloc>>>
		explicit stack(const _Alloc& _Al)
			_NOEXCEPT_COND(is_nothrow_constructible_v<_Container, const _Alloc&>) //strengthened
		: c(_Al)
		{	// construct with allocator
		}

	template<class _Alloc,
		class = enable_if_t<uses_allocator_v<_Container, _Alloc>>>
		stack(const stack& _Right, const _Alloc& _Al)
		: c(_Right.c, _Al)
		{	// construct by copying specified container
		}

	template<class _Alloc,
		class = enable_if_t<uses_allocator_v<_Container, _Alloc>>>
		stack(const _Container& _Cont, const _Alloc& _Al)
		: c(_Cont, _Al)
		{	// construct by copying specified container
		}

	explicit stack(_Container&& _Cont)
			_NOEXCEPT_COND(is_nothrow_move_constructible_v<_Container>) // strengthened
		: c(_STD move(_Cont))
		{	// construct by moving specified container
		}

	template<class _Alloc,
		class = enable_if_t<uses_allocator_v<_Container, _Alloc>>>
		stack(stack&& _Right, const _Alloc& _Al)
			_NOEXCEPT_COND(is_nothrow_constructible_v<_Container, _Container, const _Alloc&>) // strengthened
		: c(_STD move(_Right.c), _Al)
		{	// construct by moving specified container
		}

	template<class _Alloc,
		class = enable_if_t<uses_allocator_v<_Container, _Alloc>>>
		stack(_Container&& _Cont, const _Alloc& _Al)
			_NOEXCEPT_COND(is_nothrow_constructible_v<_Container, _Container, const _Alloc&>) // strengthened
		: c(_STD move(_Cont), _Al)
		{	// construct by moving specified container
		}

	void push(value_type&& _Val)
		{	// insert element at beginning
		c.push_back(_STD move(_Val));
		}

	template<class... _Valty>
		decltype(auto) emplace(_Valty&&... _Val)
		{	// insert element at beginning
#if _HAS_CXX17
		return (c.emplace_back(_STD forward<_Valty>(_Val)...));
#else /* _HAS_CXX17 */
		c.emplace_back(_STD forward<_Valty>(_Val)...);
#endif /* _HAS_CXX17 */
		}

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

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

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

	_NODISCARD 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();
		}

	const _Container& _Get_container() const
		{	// get reference to container
		return (c);
		}

	void swap(stack& _Right) _NOEXCEPT_COND(_Is_nothrow_swappable<_Container>::value)
		{	// exchange contents with _Right
		_Swap_adl(c, _Right.c);
		}

protected:
	_Container c;	// the underlying container
	};

 

二、后缀表达式求值

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

       “23.2 1.43 * 4.43 + 5.32 1.90 * +”

       其中缀表达式为:“23.2*1.43+4.43+5.32*1.90”

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

  1. 初始,栈空
  2. 遇到操作数23.2,入栈
  3. 遇到操作数1.43,入栈
  4. 遇到操作符*,弹出栈中两个元素,计算结果入栈
  5. 遇到操作数4.43,入栈
  6. 遇到操作符+,弹出占中两个元素,计算结果入栈
  7. 遇到操作数5.32,入栈
  8. 遇到操作数1.90,入栈
  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();
}
展开阅读全文

没有更多推荐了,返回首页