使用堆栈计算后缀表达式
一、实现栈结构
根据栈的先进后出的特点,很容易设置栈结构的接口:入栈、出栈、判空、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”
后缀表达式的求值规则为:从左到右扫描后缀表达式,如果遇到一个操作数,将其压入栈中,如果遇到一个操作符,则从栈中弹出两个操作数,计算结果,然后把结果入栈,直到遍历完后缀表达式,则计算完成,此时的栈顶元素即为计算结果。如上的后缀表达式求值过程为:
- 初始,栈空
- 遇到操作数23.2,入栈
- 遇到操作数1.43,入栈
- 遇到操作符*,弹出栈中两个元素,计算结果入栈
- 遇到操作数4.43,入栈
- 遇到操作符+,弹出占中两个元素,计算结果入栈
- 遇到操作数5.32,入栈
- 遇到操作数1.90,入栈
- 遇到操作符*,弹出栈中两个元素,计算结果入栈
- 遇到操作符+,弹出占中两个元素,计算结果入栈
三、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();
}