栈和队列一样也是受限制的线性表,它的特征是“先进后出,后进先出”,常常被用于一些优先级有差异的等待中。
C++的标准库为我们提供了栈的使用,下面代码示例栈的一些常用方法。
#include <stack>
#include <cstdio>
using namespace std;
int main(){
stack<int> myStack;//初始化栈,栈中元素都是int类型
printf("size of myStack = %d\n", myStack.size());//栈中元素个数
for (int i = 0; i < 10; ++i) {
myStack.push(i);//入栈
}
printf("size of myStack = %d\n", myStack.size());
while (!myStack.empty()){//栈是否为空
printf("cur top = %d\n", myStack.top());//打印栈顶元素
myStack.pop();//出栈
}
printf("myStack is empty!\n");
}
例题1 括号匹配
描述
在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用"$"标注,不能匹配的右括号用"?"标注.
输入
输入包括多组数据,每组数据一行,包含一个字符串,只包含左右括号和大小写字母,字符串长度不超过100
注意:cin.getline(str,100)最多只能输入99个字符!
输出
对每组输出数据,输出两行,第一行包含原始输入字符,第二行由"$","?"和空格组成,"$"和"?"表示与之对应的左括号和右括号不能匹配。
样例输入
((ABCD(x)
)(rttyy())sss)(
样例输出
((ABCD(x)
$$
)(rttyy())sss)(
? ?$
题目分析
(1)我们会发现,每当左括号遇到一个右括号,他们俩就匹配成功了,那我们正好可以用栈来解决,遇到左括号我们就压栈,遇到右括号就把栈中的左括号弹栈;
(2)那如何判别非法呢?左括号非法即没有右括号匹配,即它到最后还在栈中,我们可以换种思维,一开始把入栈的左括号都视为非法,每当它匹配出栈后就合法了;而右括号非法更加容易,它没有左括号匹配,即此时放左括号的栈已为空栈。
代码示例
//括号匹配
#include <stack>
#include <cstdio>
#include <string>
using namespace std;
int main(){
char buf[200];
while (fgets(buf,200,stdin) != NULL){
//fgets配合while实现不确定数量的多行读取
string str = buf;
str.pop_back();//str去掉了额外的换行
stack<unsigned> indexStack;//存储了左圆括号的下标
string res;//保存输出的结果
for(unsigned i = 0; i < str.size(); ++i){
if(str[i] == '('){
indexStack.push(i);// !!!把下标压栈
//都先认为这个左圆括号是非法的
res.push_back('$');
}
else if(str[i] == ')'){
if(indexStack.empty()){
res.push_back('?');//栈空,即没有左括号与其匹配
}
else{
res.push_back(' ');
res[indexStack.top()] = ' ';// 把$改为空格,将左括号合法
indexStack.pop();//匹配成功,该左括号出栈
}
}
else{
res.push_back(' ');//其他非括号的字符用空格代替
}
}
printf("%s\n%s\n",str.c_str(),res.c_str());
}
}
例题2 简单计算器 (浙江大学复试上机题)
描述
读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值。
输入描述:
测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用一个空格分隔。没有非法表达式。当一行中只有0时输入结束,相应的结果不要输出。
输出描述:
对每个测试用例输出1行,即该表达式的值,精确到小数点后2位。
输入:
1 + 2
4 + 2 * 5 - 7 / 11
0
输出:
3.00
13.36
题目分析
(1)对于计算表达式的问题是应用到栈最多的问题,因为对于加减乘除这种优先级差异的非常适合用栈。这类问题我们需要两个栈,一个是存储数字的num栈,一个是存储运算符的oper栈。
(2)每当要入栈的运算符优先级比栈顶运算符高时,就继续将这个运算符压栈(因为乘除法比加减法先运算,所以之前如果有加减法我们就先不算,要把乘除号入栈,以便之后弹栈时先算乘除法);当要入栈的运算符优先级比栈顶运算符低或相等时,就先把栈顶运算符弹出,并在num栈取(弹)两个数,进行运算后把结果压入num栈,然后再把之前要入栈的运算符入栈(例如前面进栈了一个除法,此时要入的是乘或者加减,都不用入,因为按从左到右和先算乘除来看,就应把栈中的除法先算完)。
(3)这个题还有一个要注意的点是如何存输入的数?我们一开始输入的是一串数字加符号的字符串,但我们发现每个数之后都有一个空格,因此我们可以每次把空格前的字符串保存下来,然后用stod() 方法把字符串变为数。此外,最后一个数的后面没有空格,所以我们需要添加一个虚假的终止符$,用同样的方法存储最后一个数。
代码示例
//简单计算器
#include <stack>
#include <string>
#include <cstdio>
#include <map>
using namespace std;
int main(){
char buf[300];
//map用来存储优先级
map<char,int> priority = {
{'$',0},
{'+',1},{'-',1},
{'*',2},{'/',2},
};
while(fgets(buf,300,stdin) != NULL){
string expr = buf;
expr.pop_back();
if(expr == "0"){
break;
}
expr.push_back('$');//补充一个虚拟的终止符
string num; //用来搜集单独的0-9以组成一个数字 1 2 3 4 --》 1234
stack<double> numstack;
stack<char> operstack;
for (unsigned i = 0; i < expr.size(); ++i) {
if(expr[i] >= '0' && expr[i] <= '9'){
num.push_back(expr[i]);
}
else if(expr[i] == ' '){
if(num != ""){
numstack.push(stod(num));//stod --> string to double
num = "";
}
}
else{
// +-*/$
if(expr[i] == '$'){
if(num != ""){
numstack.push(stod(num));//stod --> string to double
num = "";
}
}
//计算比expr[i]优先级更高的运算符
while (!operstack.empty()&&priority[operstack.top()] >= priority[expr[i]]){
//新来的运算符的优先级不高于栈顶的优先级
char oper = operstack.top();
operstack.pop();
double rhs = numstack.top();
numstack.pop();
double lhs = numstack.top();
numstack.pop();
switch (oper) {
case '+':
numstack.push(lhs + rhs);
break;
case '-':
numstack.push(lhs - rhs);
break;
case '*':
numstack.push(lhs * rhs);
break;
case '/':
numstack.push(lhs / rhs);
break;
}
}
operstack.push(expr[i]);
}
}
printf("%.2f\n",numstack.top());
}
}