栈是一种特殊的线性序列,元素只能从顶端进入,在从顶端弹出,而对栈的底端则不能进行任何操作。因此,栈是一种具有后进先出特点的数据结构。
我们可以基于vector向梁模板类来实现栈。一般来说,栈的接口有:规模size()、判空empty()、入栈push()、出栈pop()、取顶top()。栈的实现如下:
#include <vector>
Template <typename T>
class stack<T> public vector
{
void push (T const &e)
{ insert(size(), e); }
T & pop()
{ return remove(size()); }
T & top()
{ return (*this)[size()]; }
}
有几种实例特别适合使用栈,包括逆序输出、递归调用、延迟缓冲以及部分特殊运算。以下讨论几个栈应用的实例:
1.转化进制。欲将十进制的数转化为n进制的数,通常是通过长除法依次得到从低位到高位的值。在这一过程中就存在着逆序输出的必要。另外,由于事先无法把握转化后输出的长度,因此,使用栈来记录转化的数值是再合适不过的。
Stack<char> & convert (int x, int n)
{
char digit[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
Stack<char> S;
while (x > 0)
{
S.push(digit[x % n]);
x /= n;
}
return & S;
}
如此一来,返回的栈S中就逆序存放着进制转化后的数值。
2.括号匹配。对于一个表达式,要想确认其中所使用的括号是否匹配,可以采用减而治之的思路,将每对邻近括号消去,则剩下的达式括号匹配当且仅当原表达式括号匹配。利用这一思路,我们可以采用栈结构来实现:
bool paren (const char exp[], int n)
{
Stack S;
int j = 0;
for (int i = 0; i < n; i ++)
{
if (exp[i] == '(') S.push(++ j);
if (!S.empty() && (exp[i] == ')'))
S.pop();
if (S.empty() && (exp[i] == ')'))
return false;
}
return S.empty();
}
3.中缀表达式求值。所谓中缀表达式,即二元操作符位于相关操作数之间的的表达式。当我们对它进行运算时,优先考虑的问题是运算的次序。假设一个表达式合法,那么我们首先依照括号来确定各个部分的运算次序,其次才是依照各个运算符的优先级与结合性确定具体的运算次序。具体地说,每一个数的前后都有运算符(不妨设为二元的),那么只有当两个数中间的运算符优先级高于两边时,才会率先进行运算。因此,只要通过一张准确的优先级表,我们就可以在顺序读入中缀表达式的前提下顺利的进行运算了。在这里,我们采用栈结构来实现中缀表达式。
float evaluate(const char exp[])
Stack<float> opnd;
Stack<char> optr;
for (int i = 0; exp[i];)
if (isdigit(exp[i]))
{
opnd.push(exp[i]);
i ++;
}
else
switch(orderbetween(optr.top(), exp[i]))
{
case '<': optr.push(exp[i]); i ++; break;
case '=': optr.pop(); i ++; break;
case '>':
{
char op = optr.pop();
if (op == '!')
opnd.push(calcu(op, opnd.pop()));
else
float op2 = opnd.pop();
float op1 = opnd.pop();
float rs;
rs = calcu(op1, op, op2);
opnd.push(rs);
}
default: return NULL;
}
return (optr.empty()) ? opnd.pop(): NULL;
注:上述算法的实质是通过存放操作数及操作符的两个栈来将原先的中缀表达式转化为后缀表达式加以计算。