栈的应用
栈具有先进后出的特点,这个特点在解决某些问题时是很有效的。本节我们来看几个栈的常见应用以及栈结构适合解决的问题类型。
1 数制转换
我们日常生活中用的是十进制,而计算机中绝大多数时候是二进制,八进制或十六进制,这就涉及到数制转换的问题。十进制向其他进制转换一般是通过连除实现的,例如,如果我们想找出十进制的 42 对应的二进制表示,就需要对 42 连除 2,直到商为 0 。下图展示了完整过程:
用公式概括就变为了:
N
=
(
N
d
i
v
d
)
×
d
+
N
m
o
d
d
N = (N\ \mathrm{div} \ d ) \times d + N\ \mathrm{mod}\ d
N=(N div d)×d+N mod d
其中,
d
i
v
\mathrm{div}
div 为整除运算,
m
o
d
\mathrm{mod}
mod 为求余运算。
假设现在要编写一个程序来将任意的十进制数转换为八进制数。由于上述方法最先计算出的余数位于最低位,最后计算出的位于最高位,符合后入先出的思想,所以可以用栈来保存余数。
/*
* Function: 进制转换函数
* ----------------------------
* 输入一个十进制数,转换为对应的八进制表示。
*/
void conversion(int n){
int r; // 用来保存余数
LinkedStack S;
InitStack(S);
while (n!=0){
r = n % 8; // 求余数
n = n / 8; // 将n更新为商,以便后续运算
Push(S, r); // 将余数入栈
}
Print(S); // 将栈内元素输出
}
2 括号匹配问题
括号匹配问题是关于栈的结构考察中最常见的一类问题,也是很能体现栈的结构特点的一类问题。假设一个表达式中可以包含三种括号:小括号,中括号和大括号,我们要用程序来检测这个表达式中每一个左括号是否都有以正确形式与之对应的右括号。例如:
// 为了表示方便,表达式中只写出括号
{[()]} // 正确
{}[()]() // 正确
)[] // 错误,缺失对应的括号
[(]) // 错误,对应形式不正确
让我们先以 “{ [ ( ) ] }” 为例,分析一下如何正确的检测。
- 首先我们会接收第一个左大括号 “{”,为了与之匹配,我们希望尽快接到一个右大括号 “}”。
- 然后我们会接收到一个左中括号 “[”。此时,我们希望尽快接收到一个右中括号来正确匹配,否则就会出现如 “{ [ }” 的错误匹配。因此,我们最紧急的任务就从匹配大括号变为了匹配中括号。
- 之后我们会接收到一个左小括号 “(”,按照上述分析,我们最紧急的任务又变成了匹配小括号。
- 接收右小括号,完成匹配并消除小括号,开始处理中括号的匹配。
- 接收右中括号,完成匹配并消除中括号,开始处理大括号的匹配。
- 接收右大括号,完成匹配并消除打括号,匹配完成。
通过上述分析我们发现,在解决括号匹配问题的过程中,我们提出了一系列的任务,最后被提出的任务拥有最高的优先级,即最紧急的任务,需要最早被完成。这一点也正好符合栈的特点:后入先出,因此可以用栈来帮助解决这个问题。详细的实现步骤如下:
- 创建一个栈用来接收待读取的括号。
- 逐个检测括号,首先检测括号表达式的第一位是否为左括号,如不是则直接返回 false;如是则压入栈中。
- 如果不是第一位且为左括号,压入栈中。
- 如果不是第一位且为右括号,将栈顶元素与该括号匹配,如能匹配则弹出栈顶元素;如不能则直接返回 false。
- 循环第3-4步直到读取完所有的括号。
- 检测栈是否为空,如果为空,则代表所有的括号全部正确匹配,返回 true;否则就代表有多余的左括号,返回 false。
上述步骤中,左括号会被压入栈内,但右括号一定不会被压入栈内,只用来检测栈顶元素是否匹配。这主要是因为在该问题中左括号和右括号有不同的作用,左括号可以被抽象地理解为待操作的数值,待解决的问题等,而右括号就是对应的操作或解决办法。具体到这个问题,左括号就代表一个待匹配的任务,而对应的右括号就代表完成了匹配任务。尝试将输入数据分为待操作的数值和对应的操作两类是用栈解决很多问题时的常用思路,在下面的问题中会有更多说明。
括号匹配问题实现代码如下:
/*
* Function: 括号匹配检测
* ----------------------------
* 检测一个表达式中的括号是否匹配正确。
* 支持的括号种类有三种:{}, []和()。
*/
bool bracketMatch(string str){
LinkedStack S;
InitStack(S);
string match = "{[(}])"; // 用来记录可能的括号输入
char bracket;
// 首先检测第一个元素是左括号还是右括号
if (match.find(str[0])>2){
return false;
}
for (int i=0;i<str.length();i++){
// 开始配对
if (match.find(str[i])<3){ // 如果是是左括号
Push(S, str[i]);
} else { // 如果是右括号
GetTop(S, bracket);
if (match.find(str[i])-3==match.find(bracket)){
Pop(S, bracket);
} else {
return false;
}
}
}
return StackEmpty(S);
}
3 行编辑程序
行编辑程序是一种简单的文本编辑程序,它的主要功能是:接收用户输入的数据并存如数据区。但是用户在进行输入时不能保证不出差错,所以如果每输入一个数据就立刻保存显然是一种不好的做法。因此,我们希望有一段缓冲区,可以接收用户输入的一行数据,如果发现错误可以及时更改。例如,可以规定 “#” 为退格符,表示前一位数据无效;如果发现一行内有太多错误,可以规定 “@” 为退行符,表示当前行无效。例如,假设程序接收了这样的两行:
whli##ile (s#*s)
outcha@putchar(*s=#++);
实际有效的是下列两行:
while (*s)
putchar(*s++);
这种编辑程序的特点就是无法使用光标选择想要修改的位置,当前操作只能修改最新输入的字符,正好符合栈只能在栈顶修改元素的特性。在这一个问题中,我们将所有的输入数据分为两类,字符和操作符。我们只定义了两个操作符对字符进行操作,且都是对位于操作符之前的字符进行操作,所以结合栈的结构还是很好理解的。具体实现时,可以逐个检测输入的字符,如果是字符则存入栈内,如果是符号则弹出栈顶或是栈内全部元素。
4 综合计算器
下面来想一个稍复杂一点的问题,如何用栈实现一个简单的综合计算器。这个综合计算器有加减乘除四种操作,出于简单考虑,操作数仅限于整数。我们想输入一个表达式,然后让综合计算器来计算这个表达式,例如:计算 3+6*2-2。
第一眼看上去这个问题和行编辑程序问题很类似,输入数据都可以分为两类:操作数和操作符。但也有两点区别:一,计算器的操作符通常是二元的,即操作符左右两边的数都是它的操作数;二,计算器的操作符有不同的优先级,如乘除的优先级高于加减。
结合之前的想法,我们将输入数据分为操作数和操作符,依次读取表达式的元素,如果为数值就压入栈中;如果为操作符就开始对栈顶元素进行计算。那么首先将 3 入栈,然后检测到加法操作符,将 3 出栈并读取下一位数据 6,计算他们的和。但这时就会发现一个问题,原式中 6 应该属于优先级更高的乘法操作符的操作数,直接将它给予加法操作符会导致计算顺序产生错误。因此,我们还需要一些额外的信息来保存操作符的优先级,优先级最高的操作符应该是最早被执行的。而我们之前提到,栈的元素顺序就可以表示优先级,越靠近栈顶优先级越高。因此可以用栈来保存操作符,只需要将优先级最高的操作符放在栈顶即可。
那么,该问题的解决思路就是:
- 创建两个栈分别记录数字和符号。
- 遍历表达式,如果为数字,则直接入数字栈。
- 如果为符号,检测符号栈是否为空,如果为空,直接入栈。
- 如果不为空,
4.1. 待入栈符号的优先级高于或等于栈中符号的优先级,则直接入栈。
4.2.若待入栈符号的优先级小于栈中符号的优先级,则将数字栈前两位出栈,将当前符号栈中第一位出栈,运算后得到数字加入数字栈中,再加入符号。 - 重复第 2-4 步直到遍历完成。
- 若栈为空则直接返回;若栈不为空则将数字栈前两位出栈,符号栈第一位出栈,运算后得到数字加入数字栈中。
- 重复第 6 步直到数字栈中只有一位数字
注意: 上述第 4 步需要判断符号的优先级,虽然经常说加减属于同一优先级,但是加法满足交换律和结合律,而减法不满足,所以此处减法的优先级应当高于加法。例如:我们想计算 3-12+1 = -8,如果将加法和减法视为同样优先级的操作,按照上面的操作可得下图,那么最终计算出来的结果会是 3-(12+1) = -10,这显示是错误的。乘除同理。
以下只有部分代码,完整代码见附录。
/* Global variable */
stack<int> SN; // 数字栈
stack<int> SO; // 符号栈,用数字-4,-3,-2,-1来表示加,减,乘,除
string op = "+-*/"; // 记录四种操作符
/*
* Function: 判断数字函数
* Usage: bool b = isNumber(c);
* ----------------------------
* 判断输入的字符是否属于数字,
* 如是则返回true;否则返回false。
*/
bool isNumber(char c);
/*
* Function: 读取函数
* Usage: int read = readStr(str, i)
* ---------------------------------
* 用来读取表达式位置i之后的数字或符号。
* 如果读取的是数字,则直接返回数字。
* 如果读取的是符号,将加减乘除分别以整数-4,-3,-2,-1返回。
*/
int readStr(string str, int &i);
/*
* Function: 计算函数
* Usage: int res = caculate(num1, num2, op);
* ------------------------------------------
* 用来计算运算结果并返回。
*/
int caculate(int num1, int num2, int op);
/*
* Function: 综合计算器
* ----------------------------
* 可以处理加减乘除在整数范围内的运算。
* 不包括括号。
*/
int counter(string str){
int i = 0; // 用来遍历表达式字符串
int read = 0; // 用来保存读取到的值
int res = 0; // 用来保存计算的结果
int num1, num2; // 用来保存从数字栈中弹出的值
int op; // 保存从符号栈中弹出的符号
// 遍历字符串
while (i<str.length()){
read = readStr(str, i);
if (read>=0){ // 如果读取的是数字
SN.push(read); // 将数字压入栈中
} else { // 如果读取的是符号
if (SO.empty() || SO.top()<read){ // 如果符号栈为空或待入栈符号的
SO.push(read); // 优先级更高,直接压入
} else { // 否则,先运算再压入
while (!SO.empty() && read< SO.top()){
num2 = SN.top(); SN.pop();
num1 = SN.top(); SN.pop();
op = SO.top(); SO.pop();
res = caculate(num1, num2, op);
SN.push(res); // 将新计算出来的值压入栈中
}
SO.push(read);
}
}
}
// 清空栈
while (!SO.empty()){
num2 = SN.top(); SN.pop();
num1 = SN.top(); SN.pop();
op = SO.top(); SO.pop();
res = caculate(num1, num2, op);
SN.push(res); // 将新计算出来的值压入栈中
}
return SN.top();
}
注意: 上述代码在只涉及整数(要求操作数必须是整数,操作的结果也必须是整数)时,可正常实现表达式的求值。但是在操作数为整数,操作结果不为整数时会和实际值有一些不同,这主要是因为C++的 “/” 符号代表的是求整数商。例如:计算 1+2*3/4 时,程序会先计算 3/4,整数商为 0,整个表达式结果为 1,不符合实际。 只要将操作数类型从整数类型扩展为浮点数类型就可解决这个问题。
5 后缀表达式 Reverse Polish
通过综合计算器的实现,我们可以发现,对于计算机来说,即使是一个很简单的表达式也需要很繁琐的步骤来计算它。不过这主要是因为我们没有把表达式翻译为计算机可以理解的格式,在计算时需要用很多判断语句来帮助其理解正确的运算顺序。现在我们来介绍一种计算机更容易看懂的表达式——后缀表达式。
数学表达式可分为三种:前缀、中缀和后缀。我们一般使用的都是中缀表达式,即运算符在数字之间,这种表示方式人类很容易理解,但不利于计算机做运算。因此产生了前缀表达式(波兰式)和后缀表达式(逆波兰式),前和后分别表示二元运算符位于运算对象之前和之后,这两种表达式仅依靠入栈,出栈两种操作就可以完成中缀表达式的全部运算。
假如我们有表达式 (3+4)*5-6 ,那么其中缀、前缀及后缀表达式分别为:
- 中缀表达式: (3+4)*5-6
- 前缀表达式:- * + 3 4 5 6
- 后缀表达式:3 4 + 5 * 6 -
可以观察到前缀和后缀表达式都不含括号,这是因为他们经过格式转换后,符号的顺序就代表了计算的顺序,因此不再需要括号。我们上面实现的简易综合计算器就是一种中缀计算器,它其实更适合用树来实现,而前缀和后缀计算器更适合用栈实现。
5.1 后缀表达式
又称为逆波兰式,比前缀表达式更加常用,二元运算符总是置于与之相关的两个运算对象之后。例如,3 4 + 5 * 6 -,加号位于它的两个运算对象之后,第一个数 3 是加号的左操作数,第二个数 4 是加号的右操作数。
给定一个后缀表达式,它的运算规则如下:
运算规则
- 创建一个栈,用来保存数字
- 从左至右扫描后缀表达式,将数字依次存入数字栈
- 遇到运算符时,弹出栈顶的两个数
- 计算结果,压入数字栈中
- 重复上述步骤直到扫描到表达式的最右边
上述过程简洁易懂,可以看出后缀表达式在计算上的优势。这也表示如果有已经“翻译”好的表达式,计算机是可以进行快速又简洁的运算的。因此,我们再来看一下如何将中缀表达式转换为后缀表达式。
转换规则
- 创建两个栈,分别保存数字和运算符
- 从左到右遍历中缀表达式,如果是数字,就直接压入数字栈中
- 如果是 “(”,就直接压入符号栈
- 如果是 “)”,就从符号栈中弹出栈顶符号,压入数字栈中,重复该步骤直到将 “(” 弹出
- 如果是非括号符号+ - * /,则:
- 如果符号栈为空,直接压入符号栈
- 如果符号栈不为空且栈顶为 “(”,直接压入符号栈
- 如果符号栈不为空且栈顶为非括号运算符,判断符号优先级。如果当前符号优先级高于或等于栈顶符号优先级,则直接压入符号栈
- 如果当前符号优先级低于栈顶符号优先级,则需要从符号栈中弹出栈顶符号并压入数字栈中,重复该步骤直到当前符号优先级高于或等于栈顶符号优先级
- 重复第 2-5 步直到遍历整个中缀表达式
- 将符号栈元素出栈,压入数字栈中
- 最终数字栈中从栈底往栈顶方向就是完整的后缀表达式
注意:上述方法的转换结果有些问题,不符合从左向右的运算规则。 例如计算 “A+B*(C-D)-E/F” , 用上述方法得到的后缀表达式为 “ABCD-*EF/-+”,按规则计算该后缀表达式,得如下步骤:
- (C-D)
- B*(C-D)
- E/F
- B*(C-D)-E/F
- A+B*(C-D)-E/F
其中前三步都没有问题,但是到了第四步我们已经分别计算出了该表达式的三个部分:A、B*(C-D) 和 E/F,接下来按照从左向右的顺序计算即可。但是按照上述规则,我们先计算了减法部分,后计算的加法部分。这样做对答案不会产生影响,但是违反了从左向右计算的运算规则,所以得到的其实是错误的后缀表达式。(以后修改代码)
2023.10.02更新
步骤5.3中,如果当前符号优先级高于栈顶符号优先级则直接压入符号栈。
步骤5.4中,如果当前符号优先级低于或等于栈顶符号优先级,则需要从符号栈中弹出栈顶符号并压入数字栈中。
部分实现代码如下,完整代码见附录:
/* Global variable */
LinkedStack SN; // 数字栈
LinkedStack SO; // 符号栈
string op = "(+-*/)"; // 用来匹配符号,符号的位序越小代表其优先级越低
/*
* Function: 判断数字函数
* Usage: bool b = isNumber(c);
* ----------------------------
* 判断输入的字符是否属于数字,
* 如是则返回true;否则返回false。
*/
bool isNumber(char c);
/*
* Function: 读取函数
* Usage: int read = readStr(str, i);
* ---------------------------------
* 用来读取表达式位置i之后的数字或符号。
* 如果读取的是数字,则直接返回数字。
* 如果读取的是符号,将(,+,-,*,/,)分别以
* 整数-6,-5,-4,-3,-2,-1返回。
*/
int readStr(string str, int &i);
void print();
/*
* Function: 转换函数
* Usage: conversion(string str);
* ------------------------------
* 输入一个中缀表达式,将其转换为后缀表达式,
* 存入SO中并输出后缀表达式。
*/
void conversion(string str){
int i=0; // 用来遍历中缀表达式
int read=0; // 用来保存读取到的值
int res=0;
// 遍历表达式
while (i<str.length()){
read = readStr(str, i);
if (read>=0){ // 如果读取的是数字
Push(SN, read); // 将数字压入数字栈中
} else { // 如果读取的是符号
if (read==-6){ // 1.如果是左括号,则直接压入符号栈
Push(SO, read);
}
else if (read==-1){ // 2.如果是右括号,则将栈顶符号弹出并
while (GetTop(SO)!=-6){ // 压入数字栈,直到遇见左括号
Push(SN, Pop(SO));
}
Pop(SO); // 将左括号弹出
}
else if (StackEmpty(SO) || GetTop(SO)<read){// 3.如果符号栈为空或待入栈符号的
Push(SO, read); // 优先级更高,直接压入符号栈
}
else { // 4.如果符号栈非空且栈顶元素优先级更高,
while (!StackEmpty(SO) && read< GetTop(SO)){ // 则将栈顶符号弹出并压入数字栈,
Push(SN, Pop(SO)); // 直到栈顶元素优先级低于待入栈符号
}
Push(SO, read); // 将待入栈符号压入符号栈
}
}
}
// 将符号栈清空
while (!StackEmpty(SO)){
Push(SN, Pop(SO));
}
// 将数字栈中的元素倒序,存入符号栈中
while (!StackEmpty(SN)){
Push(SO, Pop(SN));
}
// 输出
print();
}
5.2 前缀表达式
又称为波兰式,前缀表达式和后缀表达式的作用一样,都是为了方便计算机计算。表达式 (3+4)*5-6 的前缀表达式为 - * + 3 4 5 6。前缀表达式的运算规则和后缀表达式很类似,唯一的区别就是遍历方向不同。
运算规则
- 从右至左扫描前缀表达式,将数字依次存入数字栈
- 遇到运算符时,弹出栈顶的两个数
- 计算结果,压入数字栈中
- 重复上述步骤直到扫描到表达式的最左边
转换规则
转换规则和后缀表达式类似,只是要注意从右到左扫描中缀表达式,且左右括号的优先级互换,剩下的操作和后缀表达式一致。详情见后缀表达式 。
相关章节
第一节 【绪论】数据结构的基本概念
第二节 【绪论】算法和算法评价
第三节 【线性表】线性表概述
第四节 【线性表】线性表的顺序表示和实现
第五节 【线性表】线性表的链式表示和实现
第六节 【线性表】双向链表、循环链表和静态链表
第七节 【栈和队列】栈
第八节 【栈和队列】栈的应用
第九节 【栈和队列】栈和递归
第十节 【栈和队列】队列
附录
综合计算器的实现
/*
* Filename: Counter.cpp
* -----------------------
* 用栈实现综合计算器。
*/
#include <iostream>
#include <string>
#include <stack>
using namespace std;
/* Prototype */
bool isNumber(char c);
int readStr(string str, int &i);
int caculate(int num1, int num2, int op);
int counter(string str);
/* Global variable */
stack<int> SN; // 数字栈
stack<int> SO; // 符号栈,用数字-4,-3,-2,-1来表示加,减,乘,除
string op = "+-*/"; // 记录四种操作符
/* Main program */
int main(){
string str = "4/2-6/2+1*3";
int res = counter(str);
printf("The result is %d.\n", res);
return 0;
}
/*
* Function: 判断数字函数
* Usage: bool b = isNumber(c);
* ----------------------------
* 判断输入的字符是否属于数字,
* 如是则返回true;否则返回false。
*/
bool isNumber(char c){
return (48<=c && c<=57);
}
/*
* Function: 读取函数
* Usage: int read = readStr(str, i);
* ---------------------------------
* 用来读取表达式位置i之后的数字或符号。
* 如果读取的是数字,则直接返回数字。
* 如果读取的是符号,将加减乘除分别以整数-4,-3,-2,-1返回。
*/
int readStr(string str, int &i){
string num; // 用来读取数字
int n = 0; // 用来记录多位数在字符串中的下标
if (isNumber(str[i])){ // 如果读取到的是数字
while (i+n<str.length() && isNumber(str[i+n])){
num[n] = str[i+n]; // 将读取到的连续数字存入num中
n++;
}
i = i+n; // 更新i的值
return stoi(num);
}
return op.find(str[i++])-4; // 如果读取到的是操作符,则直接转换为数字返回
}
/*
* Function: 计算函数
* Usage: int res = caculate(num1, num2, op);
* ------------------------------------------
* 用来计算每一步计算的结果并返回。
*/
int caculate(int num1, int num2, int op){
int res=0;
switch(op){
case -4: // 加法
res = num1+num2;
break;
case -3: // 减法
res = num1-num2;
break;
case -2: // 乘法
res = num1*num2;
break;
case -1: // 除法
res = num1/num2;
break;
}
return res;
}
/*
* Function: 综合计算器
* ----------------------------
* 可以处理加减乘除在整数范围内的运算。
* 不包括括号。
*/
int counter(string str){
int i = 0; // 用来遍历表达式字符串
int read = 0; // 用来保存读取到的值
int res = 0; // 用来保存计算的结果
int num1, num2; // 用来保存从数字栈中弹出的值
int op; // 保存从符号栈中弹出的符号
// 遍历字符串
while (i<str.length()){
read = readStr(str, i);
if (read>=0){ // 如果读取的是数字
SN.push(read); // 将数字压入栈中
} else { // 如果读取的是符号
if (SO.empty() || SO.top()<read){ // 如果符号栈为空或待入栈符号的
SO.push(read); // 优先级更高,直接压入
} else { // 否则,先运算再压入
while (!SO.empty() && read< SO.top()){
num2 = SN.top(); SN.pop();
num1 = SN.top(); SN.pop();
op = SO.top(); SO.pop();
res = caculate(num1, num2, op);
SN.push(res); // 将新计算出来的值压入栈中
}
SO.push(read);
}
}
}
// 清空栈
while (!SO.empty()){
num2 = SN.top(); SN.pop();
num1 = SN.top(); SN.pop();
op = SO.top(); SO.pop();
res = caculate(num1, num2, op);
SN.push(res); // 将新计算出来的值压入栈中
}
return SN.top();
}
后缀表达式
链栈定义及实现
/*
* Filename: LinkedStack.h
* -----------------------
* 使用单链表来实现链栈
*/
#ifndef _SINGLE_LINKED_LIST_h_
#define _SINGLE_LINKED_LIST_h_
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
/********** 链栈的结点的类型定义 **********/
typedef int ElemType;
// typedef char ElemType;
typedef struct LNode{
ElemType data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkedStack;
/********** 主要操作的实现 **********/
/*
* Function: 初始化操作
* ----------------------------
* 初始化一个空栈
*/
void InitStack(LinkedStack &S){
S = new LNode;
S->next = NULL;
}
/*
* Function: 判空操作
* ----------------------------
* 判断栈S是否为空,若为空则返回true,否则返回false。
*/
bool StackEmpty(LinkedStack S){
return !S->next;
}
/*
* Function: 读取栈顶元素操作
* ----------------------------
* 返回栈顶元素。
*/
ElemType GetTop(LinkedStack S){
return S->next->data;
}
/*
* Function: 入栈操作
* ----------------------------
* 使用头插法将e插入,使之成为新栈顶。
*/
void Push(LinkedStack &S, ElemType e){
LNode *n = new LNode;
n->data = e;
n->next = S->next;
S->next = n;
}
/*
* Function: 出栈操作
* ----------------------------
* 弹出栈顶元素并返回。
*/
ElemType Pop(LinkedStack &S){
LinkedStack q;
ElemType e;
q = S->next;
e = q->data;
S->next = q->next;
delete q;
return e;
}
/*
* Function: 输出操作
* ----------------------------
* 由于单链表的特性,更方便按从栈尾到栈头的顺序输出。
* 后续可以用递归法来正向输出。
*/
void Print(LinkedStack S){
LinkedStack tmp=S;
while (tmp->next!=NULL){
tmp = tmp->next;
// printf("%d <- ", tmp->data);
printf("%d ", tmp->data);
}
// printf("base\n");
printf("\n");
}
#endif // _SINGLE_LINKED_LIST_h_
后缀表达式转换及运算的实现
/*
* Filename: ReversePolish.cpp
* -----------------------
* 用栈实现中缀表达式向后缀表达式转换
* 以及计算后缀表达式的程序。
*/
#include <iostream>
#include <string>
#include "../LinkedStack/LinkedStack.h"
using namespace std;
/* Global variable */
LinkedStack SN; // 数字栈
LinkedStack SO; // 符号栈
string op = "(+-*/)"; // 用来匹配符号,符号的位序越小代表其优先级越低
/* Prototype */
bool isNumber(char c);
int readStr(string str, int &i);
void print();
void conversion(string str);
int caculate(int num1, int num2, int op);
int calculateReverse();
/* Main program */
int main(){
InitStack(SN);
InitStack(SO);
int res=0;
string str = "1+2/2*(8-4)"; // "(3+4)*5-6"
printf("Reverse polish is: ");
conversion(str);
res = calculateReverse();
printf("The result is: %d\n", res);
return 0;
}
/*
* Function: 判断数字函数
* Usage: bool b = isNumber(c);
* ----------------------------
* 判断输入的字符是否属于数字,
* 如是则返回true;否则返回false。
*/
bool isNumber(char c){
return (48<=c && c<=57);
}
/*
* Function: 读取函数
* Usage: int read = readStr(str, i);
* ---------------------------------
* 用来读取表达式位置i之后的数字或符号。
* 如果读取的是数字,则直接返回数字。
* 如果读取的是符号,将(,+,-,*,/,)分别以
* 整数-6,-5,-4,-3,-2,-1返回。
*/
int readStr(string str, int &i){
string num; // 用来读取数字
int n = 0; // 用来记录多位数在字符串中的下标
if (isNumber(str[i])){ // 如果读取到的是数字
while (i+n<str.length() && isNumber(str[i+n])){
num[n] = str[i+n]; // 将读取到的连续数字存入num中
n++;
}
i = i+n; // 更新i的值
return stoi(num);
}
return op.find(str[i++])-6; // 如果读取到的是操作符,则直接转换为数字返回
}
void print(){
LinkedStack tmp=SO;
while (tmp->next!=NULL){
tmp = tmp->next;
if (tmp->data>=0){
printf("%d ", tmp->data);
} else {
switch(tmp->data){
case -5:
printf("+ ");
break;
case -4:
printf("- ");
break;
case -3:
printf("* ");
break;
case -2:
printf("/ ");
break;
}
}
}
printf("\n");
}
/*
* Function: 转换函数
* Usage: conversion(string str);
* ------------------------------
* 输入一个中缀表达式,将其转换为后缀表达式,
* 存入SO中并输出后缀表达式。
*/
void conversion(string str){
int i=0; // 用来遍历中缀表达式
int read=0; // 用来保存读取到的值
int res=0;
// 遍历表达式
while (i<str.length()){
read = readStr(str, i);
if (read>=0){ // 如果读取的是数字
Push(SN, read); // 将数字压入数字栈中
} else { // 如果读取的是符号
if (read==-6){ // 1.如果是左括号,则直接压入符号栈
Push(SO, read);
}
else if (read==-1){ // 2.如果是右括号,则将栈顶符号弹出并
while (GetTop(SO)!=-6){ // 压入数字栈,直到遇见左括号
Push(SN, Pop(SO));
}
Pop(SO); // 将左括号弹出
}
else if (StackEmpty(SO) || GetTop(SO)<read){// 3.如果符号栈为空或待入栈符号的
Push(SO, read); // 优先级更高,直接压入符号栈
}
else { // 4.如果符号栈非空且栈顶元素优先级更高,
while (!StackEmpty(SO) && read< GetTop(SO)){ // 则将栈顶符号弹出并压入数字栈,
Push(SN, Pop(SO)); // 直到栈顶元素优先级低于待入栈符号
}
Push(SO, read); // 将待入栈符号压入符号栈
}
}
}
// 将符号栈清空
while (!StackEmpty(SO)){
Push(SN, Pop(SO));
}
// 将数字栈中的元素倒序,存入符号栈中
while (!StackEmpty(SN)){
Push(SO, Pop(SN));
}
// 输出
print();
}
/*
* Function: 计算函数
* Usage: int res = caculate(num1, num2, op);
* ------------------------------------------
* 用来计算运算结果并返回。
*/
int caculate(int num1, int num2, int op){
int res;
switch(op){
case -5: // 加法
res = num1+num2;
break;
case -4: // 减法
res = num1-num2;
break;
case -3: // 乘法
res = num1*num2;
break;
case -2: // 除法
res = num1/num2;
break;
}
return res;
}
/*
* Function: 计算后缀表达式函数
* Usage: int res = calculateReverse(LinkedStack S);
* -------------------------------------------------
* 后缀表达式保存在栈S中,输入栈S,返回该表达式的值。
*/
int calculateReverse(){
int num1, num2, res=0;
// 遍历后缀表达式
while (!StackEmpty(SO)){
if (GetTop(SO)>=0){
Push(SN, Pop(SO));
} else {
num2 = Pop(SN);
num1 = Pop(SN);
res = caculate(num1, num2, Pop(SO));
Push(SN, res);
}
}
return GetTop(SN);
}