栈的定义
栈是限制插入和删除操作只能在一个位置上进行的表。它是一种 LIFO(Last In First Out) 线性表。
表的末端成为栈的顶(top)

栈的操作与实现
数组实现
以下代码前提:
const int N = 1009; // 按需定
int stk[N], tp;
-
push
操作(入栈)注意事项:入栈前务必要检查栈是否为满。
不过,一般情况下,栈定义的大小总比题目中要求的大,所以可以不判断是否满。
bool push(int x) { if (tp == N) return false; // 以上代码可写可不写 stk[++tp] = x; return true; }
-
pop
操作(出栈)注意事项:出栈前务必要检查栈是否为空。
bool pop() { if (tp == 0) return false; tp--; return true; }
-
isEmpty
操作(判断栈是否为空)bool isEmpty() { return tp == 0; }
-
isFull
操作(判断栈是否为满)bool isFull() { return tp == N; }
-
size
操作(栈的大小)int size() { return tp; }
-
top
操作(返回栈顶元素的引用)int top() { return stk[tp]; }
-
clear
操作(清空栈)void clear() { tp = 0; }
STL
实现
使用前,必须加上头文件 #include <stack>
& 标准名字空间 using std::stack;
示例代码:
#include <iostream>
#include <stack>
using std::stack;
stack<int> stk;
int main() {
int x = 10; // 待入栈元素
stk.push(x); // 入栈操作
printf("top=%d size=%d\n", stk.top(), stk.size()); // 返回栈顶指针的引用 & 栈的大小
printf("isEmpty=%d\n", stk.empty()); // 返回栈是否为空
if (!stk.empty()) stk.pop(); // 出栈操作
printf("size=%d\n", stk.size()); // 返回栈的大小 *PS: 此处不能输出 stk.top() !
printf("isEmpty=%d\n", stk.empty()); // 返回栈是否为空
return 0;
}
以上函数的时间复杂度均为 O ( 1 ) O(1) O(1)
值得注意的是,STL
并没有清空栈的函数,所以,如果想要清空栈,只能这么写:
while (stk.size())
stk.pop();
栈的应用
括号配对问题 / Easy
假设表达式中允许包含两种括号:圆括号和方括号,其嵌套的顺序随意,如 ( [ ] ( ) ) 或 [ ( [ ] [ ] ) ] 等为正确的匹配,[ ( ] ) 或( [ ] ( ) 或 ( ( ) ) )均为错误的匹配。
现在的问题是,要求检验一个给定表达式中的括弧是否正确匹配?
输入一个只包含圆括号和方括号的字符串,判断字符串中的括号是否匹配,匹配就输出OK
,不匹配就输出Wrong
。
题解
这题是最简单的括号配对问题。伪代码如下:
if (左括号) {
入栈;
} else if (')') {
if (栈顶不为'(')
结束程序;
出栈;
} else if (']') {
if (栈顶不为'[')
结束程序;
出栈;
}
C++ 代码:
#include <iostream>
const int N = 1 << 8;
int tp;
char s[N], stk[N];
int main() {
scanf("%s", s);
for (int i = 0; s[i]; i++) {
if (s[i]=='(' or s[i]=='[') {
stk[++tp] = s[i];
} else if (s[i] == ')') {
if (stk[tp] != '(') {
puts("Wrong");
return 0;
}
tp--;
} else if (s[i] == ']') {
if (stk[tp] != '[') {
puts("Wrong");
return 0;
}
tp--;
}
}
if (tp) puts("Wrong");
else puts("OK");
return 0;
}
括号配对问题 / Difficult
字符串中只含有括号 (),[],<>,{},判断输入的字符串中括号是否匹配。如果括号有互相包含的形式,从内到外必须是<>,(),[],{},例如。输入: [()] 输出 YES
,而输入([]),([)]都应该输出 NO
。
PS. 本题为多组输入输出
题解
本题比上题难,主要难在两点:
- 括号种类 2 2 2 个增至 4 4 4 个
- 引入优先级
其中,第一个难点可以通过定义字符串解决,第二个难点可以通过函数解决。题目整体难度不算大。
#include <iostream>
using namespace std;
const int N = 260;
int n, stk[N] = {8};
string s, priority = "<>()[]{}";
int get_pre(char c) {
for (int i = 0; i < priority.size(); i++)
if (priority[i] == c)
return i;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
while (n--) {
int tp = 0;
cin >> s;
s = ' ' + s;
int i, pre, len = s.size();
for (i = 1; i <= len; i++) {
pre = get_pre(s[i]);
if ((pre & 1) == false) { // 左括号
if (stk[tp] > pre) {
stk[++tp] = pre + 1; // 压入右括号(技巧)
} else { // 优先级错误
puts("NO");
break;
}
} else {
if (stk[tp] != pre) { // 括号不匹配
puts("NO");
break;
} else { // 弹栈
tp--;
}
}
}
if (i < len) continue; // 已经 puts("NO");
if (tp != 1) puts("NO"); // 有多余括号
else puts("YES"); // 完全匹配且优先级正确
}
return 0;
}
简易计算器
小明在你的帮助下,破密了Ferrari设的密码门,正要往前走,突然又出现了一个密码门,门上有一个算式,其中只有“(”,“)”,“0-9”,“+”,“-”,“*”,“/”,“^”,求出的值就是密码。小明数学学得不好,还需你帮他的忙。(“/”用整数除法,"^"是幂运算,2^3
为 8)
PS. 输入保证合法
题解
读者可以先尝试这道题:洛谷_P1981 [NOIP2013 普及组] 表达式求值
本题是⌈表达式求值⌋的进阶版,具有一定的难度。
这题中,有如下运算符:(
,)
,+
,-
,*
,/
,^
。由幼儿园知识易得,括号的运算优先级是最高的。所以,当我们遇到右括号时,就意味着从与它配对的左括号到它没有一个优先级比它高,这样就可以心安理得地弹栈并计算了。
那如果遇到了运算符呢?同理,不停弹栈并计算,直至栈顶运算符优先级比它高为止。
本题核心思想伪代码如下:
if (左括号) {
将左括号压进符号栈;
} else if (右括号) {
弹栈并计算;
} else if (数) {
将其压进数字栈;
} else { // 即除括号外其他运算符
弹栈直至栈顶运算符的优先级高于当前运算符的优先级;
}
C++ 代码:
#include <iostream>
#include <cstring>
#include <cmath>
#include <cctype>
const int N = 39;
char s[N] = "(", stkc[N];
int stk[N], tp, tpc;
inline int pre(char op) {
if (op=='+' or op=='-') return 1;
if (op=='*' or op=='/') return 2;
if (op=='^') return 3;
return 0;
}
void calc() {
int x = stk[tp--], y = stk[tp--];
switch (stkc[tpc--]) {
case '+': y += x; break;
case '-': y -= x; break;
case '*': y *= x; break;
case '/': y /= x; break;
case '^': y = pow(y, x); break;
}
stk[++tp] = y;
}
int read(int &cur) {
int ans = 0;
bool neg = false;
char c = s[cur];
if (c == '-') neg = true, ++cur;
while (isdigit(s[cur])) ans = ans * 10 + s[cur++] - '0';
cur--;
return neg ? -ans : ans;
}
int main() {
scanf("%s", s + 1);
s[strlen(s)] = ')';
for (int i = 0; s[i]; i++) {
if (s[i] == '(') {
stkc[++tpc] = '(';
} else if (s[i] == ')') {
while (stkc[tpc] != '(') calc();
tpc--;
} else if (isdigit(s[i]) or (s[i]=='-' and s[i-1]=='(')) { // 特别注意处理负数
stk[++tp] = read(i);
} else {
while (pre(s[i]) <= pre(stkc[tpc])) calc();
stkc[++tpc] = s[i];
}
}
printf("%d", stk[tp]);
return 0;
}
逆波兰表达式
逆波兰记法中,操作符置于操作数的后面。例如表达“三加四”时,写作“3 4 +”,而不是“3 + 4”。如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 - 4 + 5”在逆波兰记法中写作3 4 - 5 +”:先3减去4,再加上5。
使用逆波兰记法的一个好处是不需要使用括号。
PS. 保证式子正确,运算符只包括 加+
、减-
、乘*
、除/
PS. 输出一个浮点数,保留两位有效数字
题解
按说这题比上题简单,因为没有什么乱七八糟的优先级问题。例如 1 2 + 3 *
,当栈中有已经两个数,且当前字符为运算符,就 calc,否则继续压栈。
#include <iostream>
#include <cstring>
#include <stack>
using namespace std;
stack <double> stk;
char s[1009], ch[109];
void calc(char c) {
double x = stk.top(); stk.pop();
double y = stk.top(); stk.pop();
switch (c) {
case '+': y += x; break;
case '-': y -= x; break;
case '*': y *= x; break;
case '/': y /= x; break;
}
stk.push(y);
}
int main() {
int cur = 0;
cin.getline(s, 1009);
s[strlen(s)] = ' ';
for (int i = 0; s[i]; i++) {
if (s[i] == ' ') continue;
while ((s[i] >= '0' and s[i] <= '9') or s[i] == '.' or (s[i] == '-' and isdigit(s[i+1])))
ch[cur++] = s[i++];
if (isdigit(ch[0]) or ch[0] == '-') stk.push(atof(ch));
else calc(s[i]);
cur = 0;
memset(ch, 0, sizeof(ch));
}
printf("%.2f", stk.top());
return 0;
}
PS. 这个代码是以前写的,码风与其他代码不太一致,敬请谅解。
拓展 / 系统栈
利用递归写一个 10 10 10 进制转 2 2 2 进制的程序。
这个程序大家都会写:
void trans(int n) {
if (n) {
trans(n >> 1);
printf("%d", n & 1);
}
}
模拟将数 10 10 10 转为 2 2 2 进制的过程:
由上图,递归与回溯的过程其实就是压栈与弹栈。所以所有的 D F S DFS DFS 都可以用栈来解决。
下一篇文章将讲解 D F S DFS DFS 。