本文简要介绍了栈与队列的基本使用方法并列举了进制转换、括号匹配、逆波兰表达式等经典应用。
一、栈
栈也是一种存储多个数据的结构,其特点是只有一端开口,数据的访问与更改也只能从该段进行及后进先出!如下图所示,向栈中插入元素的操作成为push,反之成为pop。
二、栈的接口函数
函数名 | 用途 |
---|---|
Stack() | 创造空栈 |
push(x) | 顶端插入x |
pop() | 取出顶端元素 |
empty() | 判断栈内是否有元素 |
size() | 栈中元素个数 |
top() | 返回顶端元素 |
三、栈应用
利用栈后进先出的特点,可以巧妙实现多种算法,这里简单列举部分经典算法。
1.进制转换——栈的逆序输出
栈结构最突出的特点便是逆序输出特性,该示例便是利用此特性方便地实现进制转换功能。
1)进制转化方法
众所周知,进制转换最直接的方法为短除后将余数逆序排列。先将10进制转2进制的示例展示如下,其余进制间转化与该方法类似。
不难想象,在程序中将每次算出的余数push进栈结构中,计算结束后再逆序将其pop出来便可得到相应结果。
2)算法实现
void convert( Stack <char> &s, n, base)
{
//考虑到16进制等常用较大进制从存在,将s声明为字符型数组并对digit做如下定义
static char digit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
while (n > 0) //模仿短除操作,并将余数一次存入栈中。
{
s.push(digit[n % base]);
n /= base;
}
}
主函数中已经得到正确的栈s,只需用pop将其依次逆序输出即可。
main()
{
Stack <char> s;
int n;
int base;
//...
convert(s, n, base); //进行余数运算
while(!s.empty()) //将结果逆序输出得到答案
cout << s.pop;
}
2.括号匹配——栈的递归嵌套
当需解决的问题可递归描述且递归深度未知时,可巧妙利用栈的结构特点解决问题。
1)问题描述
我们编写程序时,需要保证左右括号的匹配问题,若出现括号不匹配的情况,则程序无法被编译。
如下图所示,上式中括号完全匹配而下式中的橙色括号缺失。
需要特别注意的是,并不是同种类左右括号数量相等及为匹配。反例如下:(该示例说明不能用简单计数器解决该问题)
2)解决思路
(1)该问题用栈结构可巧妙解决:
(2)首先通过简单扫描去除多余元素,使得待判断字符串只包含括号。
(3)之后逐个扫描字符串中元素,每当扫描到左括号便将其从顶端插入栈中。当扫描到右括号时,检测此括号是否与栈顶端括号相匹配,若匹配,则取出该元素并进行后续判断,否则说明字符串中括号匹配有误。
(4)当字符串中括号全部扫描完成后,查看栈中是否为空。若栈中不含有任何元素,则该字符串中各组括号匹配,否则匹配有误。
3)代码实现
bool paren(const char exp[], int lo, int hi)
{
Stack<char> s;
for(in i = lo; i < hi; i++) //在exp[lo]~exp[hi]间查找
if( exp[i] == '(' || exp[i] == '[' && exp[i] == '{') //判断是否为左括号
s.push(exp[i]);
else if (!s.empty())
if(exp[i] - s.top() <= 2) //判断是否为对应的右括号
s.pop();
else return false;
else return false;
return s.empty();
}
3.栈混洗
1)问题描述
现有A B S三个栈,需借助S栈的过渡按特定顺序将A栈中的数据转移至B栈中,可采用的操作方式有两种(如图)。
并非所有情况均可使用栈混洗解决,对于个数为你的序列,可进行栈混洗操作的个数为:
同样,判断一个序列A是否可以进行栈混洗需如下要求:
对于任意i < j, 不含有[…,j + 1, …, i, …, j, …>
对于程序而言,进行如下判断即可:
在每次执行s.pop()之前,检测s是否已空,或需弹出元素在s中却非顶端元素。
4.中缀表达式求值——延迟缓冲
需要预读足够多数据才可进行处理
1)问题描述
当计算机进行表达式计算时,由于运算符优先级不同,会经常出现扫描在前,运算在后的情况,此时,利用栈结构的特点可轻松解决该问题。
2)解决方案
(1)总体方案
用两个栈分别存储扫描过的数字和符号,当扫描到符号时,判断该符号与栈顶端符号的优先级,若该符号优先级更高,则将其压入栈中,若栈顶符号优先级高,则进行计算后将结果存入数字栈中。
(2)算符优先级比较
各运算符优先级见下表,在程序中可存储于二维数组。
3)代码实现
(1)主算法
float evaluate(char * s, char * & RPN)
{
Stack<float> opnd; //数字栈
Stack<char> optr; //运算符栈
optr.push('\0');
while(!optr.empty())
if(isdigit(*s))
readNumber(s,opnd); //读入数字
else
switch(orderBetween(optr.top(), *s)
{
/* 处理运算*/
}
return opnd.pop(); //将运算结果弹出
}
(2)运算符优先级判断算法
switch(orderBetween(optr.top(), *s))
{
case '<': //栈顶部运算符优先级更低
optr.push(*s); //新运算符进入
s++;
break;
case '=': //运算级相等及为括号或\0
optr.pop(); //脱括号
s++;
break;
case '>': 、、栈顶部运算优先级高,执行该运算
char op = optr.pop();
if('!' == op) //一元运算
opnd.push(calcu(op, opnd.pop()));
else //二元计算
{
float pOpnd2 = opnd.pop();
float pOpnd1 = opnd.pop();
opnd.push(calcu(pOpnd1, op, pOpnd2));
}
break;
}
5.逆波兰表达式
1)问题描述
逆波兰表达式又叫后缀表达式,是一种方便计算机计算的代数表达式。
该表达式见到运算符时只需依次进行计算即可。
2)代码
将中缀表达式转化为后缀表达式代码如下:
//该代码只需在上一问题代码中稍加修改即可
float evaluate(char* s, char* & RPN)
{
while(!optr.empty())
{
if(isdigit(*s))
{
readNumber(s,opnd);
append(RPN, opnd.top());
}
else
switch(orderBetween(optr.top(), *s))
{
/*........*/
case '>':
char op = optr.pop();
append(RPN, op);
}
}
}
四、队列
1.队列
队列的结构与栈正好相反,及先进先出后进后出。队列只存在两个口,一端为入口,一端为出口。(如图)
2.接口函数
函数名 | 作用 |
---|---|
Queue() | 创建队列 |
empty() | 判断队列是否为空 |
enqueue(x) | 输入端插入x |
dequeue() | 输出端取出数据 |
front() | 获取出口处元素 |
size() | 队列内部元素个数 |
//