栈的模板类与基本应用(还差栈混洗)
栈的模板类与基本应用
一、栈模板类的接口与实现
1.1接口如下:

1.2接口的数组实现:
原理:因为栈是一种规定了访问方式与存、取方式的线性数据结构,但是归根结底它仍然是一种线性结构,于是我们可以用一种我们所熟知的线性结构来表示它或者是派生它。
实现:
#include <iostream>
using namespace std;
const int maxn = 1e6 + 5;// 如果不用动态内存分配就只能开到 1e5
class Stack{
private:
int *data;// 存储栈中整型元素
int _top;
public:
Stack();// 构造函数
bool _push(int e);
bool _pop();
int top_();
bool _empty();
};
Stack::Stack(){
_top = -1;
data = new int[maxn];
}
bool Stack::_push(int e){
if(_top == maxn - 1)
return false;
data[++_top] = e;
return true;
}
bool Stack::_pop(){
if(_top == -1)
return false;
_top--;
return true;
}
bool Stack::_empty(){
return -1 == _top;
}
int Stack::top_(){
return data[_top];
}
int main(){
Stack s;// 用自己写的栈完成进制转换
int pre;
cin >> pre;
while(pre){
s._push(pre % 2);
pre /= 2;
}
while(!s._empty()){
cout << s.top_();
s._pop();
}
return 0;
}
这里是用数组实现的,栈模板类的C++语言实现,用到了面向对象,下次给大家总结了向量和链表之后,再用向量派生的方法来实现栈模板吧
1.3提出问题:
① STL里使用栈模板类容器和手写栈的优劣比较:
STL的栈模板类容器:
使用了重载,可以容纳各种类型的元素入栈,如果是在处理需要把某个结构体入栈的时候,就只能选择使用C++自带的栈模板
手写栈模板类:
C++自带的栈模板存储能力有限,如果出现了栈溢出的现象,手工写栈就是一种解决问题的办法!
② 在上面我给出的一个栈模板类实现中,如果把maxn再扩大十倍,也会爆栈!请各位思考,明明是用数组来实现的,数组在全局变量中开到 1e6 的级别是不会爆炸的,那为何写入栈模板类就支撑不住了呢?
关于开到1e6爆栈的缘由:
全局变量的申请是用堆,那么讲道理,类里面申请空间应该也是堆,但是我们实验发现,类里面不能开大数组,这说明类里面用直接定义数组的方法开辟空间还是在申请栈空间、申请局部内存。但是据某位研究生大佬说,在Linux下可以设置栈无限大:ulimit -s ulimit。但是我们也可以用动态申请内存的方法:在类里面开:int *_data;然后在构造函数里:_data = new int[maxn];
二、栈的基本应用一:倒序输出
其实啊,上述的那个例子,进制转换就是倒序输出的一种
因为进制转换用的是:除K取余法,余数要从下网上输出就是新的进制数,这种后进先出的顺序让我们想到可以用栈去解决!
练习1 :将一个十进制数,转换成任意进制的数,如果大于9,则用A、B、……代替。
#include <iostream>
#include <stack>
#include <algorithm>
using namespace std;
int main(){
long long n, m;
stack<int>s;
cin >> n >> m;
while(n){
s.push(n % m);
n /= m;
}
while(!s.empty()){
if(s.top() <= 9)
cout << s.top();
else
cout << char(s.top() - 10 + 'A');
s.pop();
}
return 0;
}
练习2 :
样例输入 & 输出:
附上参考代码:
#include <iostream>
#include <stack>
#include <algorithm>
using namespace std;
int main(){
stack<int>s1, s2;
char opp;
int x;
while(cin >> opp){
if(opp == '1'){
cin >> x;
s1.push(x);
if(s2.empty() || x > s2.top())
s2.push(x);
}else if(opp == '2'){
if(!s1.empty() && !s2.empty()){
if(s1.top() == s2.top()){
s1.pop();
s2.pop();
}else
s1.pop();
}else
cout << "stack is emptied !" << endl;
}else if(opp == '3')
if(!s1.empty() && !s2.empty())
cout << s2.top() << endl;
else
cout << "stack is emptied !" << endl;
}
return 0;
}
三、栈的基本应用2:括号匹配
首先来看看一张括号匹配的图,咱们先一起理解一下:
那我们有哪些方法可以实现括号匹配呢?
一:用栈:
先给大家一张图便于思考:
参考代码如下:
#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
stack <char> s;
string str;
bool check(){
for(int i = 0;i < str.length();i++){
if('(' == str[i])
s.push(str[i]);
else if(!s.empty())
s.pop();
else
return false;
}
return s.empty();
}
int main(){
cin >> str;
if(check)
cout << "YES";
else
cout << "NO";
return 0;
}
反思:如果是多种括号的匹配问题呢?
可否设置多个栈?答案是没用的,多个栈和一个栈是一样的,会有这样的一个反例:[(])
如何解决这个问题呢?我们可以发现除了这种特例之外,其他的匹配问题和一种括号是一模一样的,所以我们可以专门针对这种问题进行特判,代码如下:
#include <iostream>
#include <stack>
#include <string>
#include <algorithm>
using namespace std;
stack<char>s;
string str;
bool check2(){
for(int i = 0;i < str.length();i++){
if('(' == str[i] || '[' == str[i] || '{' == str[i])
s.push(str[i]);
else if(')' == str[i] || ']' == str[i] || '}' == str[i]){
bool book = true;
if(')' == str[i] && ('[' == str[i - 1] || '{' == str[i - 1]))
book = false;
if(']' == str[i] && ('(' == str[i - 1] || '{' == str[i - 1]))
book = false;
if('}' == str[i] && ('(' == str[i - 1] || '{' == str[i - 1]))
book = false;
if(s.empty() || !book)
return false;
else
s.pop();
}
}
return s.empty();
}
int main(){
cin >> str;
if(check2())
cout << "YES";
else
cout << "NO";
return 0;
}
二:用一个数字去模拟栈:只能解决一种括号的问题
算法思想:
我们可以把遇到左括号记作:+1,遇到右括号记作:-1
#include <iostream>
using namespace std;
int main(){
int isRight = 0, book = 0;
char ch;
while(1){
cin >> ch;
if(ch == '(')
isRight++;
if(ch == ')'){
isRight--;
if(isRight < 0)
break;
}
}
if(isRight)
cout << "NO";
else
cout << "YES";
return 0;
}
四、后缀表达式的计算
4.1 中缀表达式换算成后缀表达式
我们一般遇到的计算式,都是中缀表达式,比如:1 + 2 * 3 - 4就是一个很显然的中缀表达式
那么,中缀表达式的定义是什么呢?其实就是二叉树的中序遍历得到的节点序列
这里的节点,实际上有两种:①操作数节点,也就是被操作的对象;②操作符节点,也就是大家所熟知的加减乘除,现在我们不妨来用一个中缀表达式还原成一颗二叉树:
举个栗子:3 + 5*2/4 - 6/3
大伙还记得中序遍历吗?离散数学都还给李dong老师了吗?来来来,小良帮你复习一下下:
首先二叉树的中序遍历的顺序是:左 -> 根 -> 右
那么,较高级的运算,就必然是某个子树的根节点
还原如下:
手画比较丑,画错了记得留言告诉弱鸡小良,小良就马上改!
根据这幅图,我们来走一遍后序遍历:3524/*+63/-
那么如何用栈来实现逆波兰表达式的计算呢?
练习:后缀表达式求值
这个题的样例小良再次手绘模拟一下过程:

书写代码:
#include <iostream>
#include <stack>
#include <string>
#include <algorithm>
using namespace std;
string str;
stack<int>s;
int cal(){
for(int i = 0;str[i] != '@';i++){
if('.' == str[i]){
int cnt = 0, k = 1;
for(int j = i - 1;j >= 0 && str[j] >= '0' && str[j] <= '9';j--){
cnt = cnt + (str[j] - '0')*k;
k *= 10;
}
s.push(cnt);
continue;
}
if('0' <= str[i] && str[i] <= '9')
continue;
int sum = s.top();
s.pop();
if('+' == str[i]){
sum = s.top() + sum;
s.pop();
s.push(sum);
}else if('-' == str[i]){
sum = s.top() - sum;
s.pop();
s.push(sum);
}else if('*' == str[i]){
sum = s.top()*sum;
s.pop();
s.push(sum);
}else if('/' == str[i]){
sum = s.top()/sum;
s.pop();
s.push(sum);
}
}
return s.top();
}
int main(){
cin >> str;
cout << cal();
return 0;
}
再看一道题:
练习:计算中缀表达式的值
算法分析:
这个中缀表达式就需要两个栈:一个操作栈,一个操作数栈
然后,还有一件比较麻烦的事情,就是预处理优先级,因为每个运算的优先级不一样,比如乘除运算要高于加减运算,优先级高的,要先计算。
#include <iostream>
#include <stack>
#include <string>
#include <algorithm>
#define LL long long
using namespace std;
string str;
stack<LL>s_num;
stack<char>s_opp;
int opp(char ch){
if(ch == '+' || ch == '-')
return 1;
else if(ch == '*' || ch == '/')
return 2;
}
LL cal(){
int ans = 0;
for(int i = 0;i <= str.length();i++){
if(str[i] == ' ')
continue;
if(str[i] < '0' || str[i] > '9'){
int cnt = 0, k = 1;
for(int j = i - 1;j >= 0 && str[j] >= '0' && str[j] <= '9';j--){
cnt += (str[j] - '0')*k;
k *= 10;
}
s_num.push(cnt);
if(s_opp.empty())
s_opp.push(str[i]);
else{
char op = s_opp.top();
if(opp(str[i]) <= opp(op)){
int x = s_num.top();
s_num.pop();
int y = s_num.top();
s_num.pop();
if(op == '*')
x = x * y;
else
x = x + y;
s_num.push(x);
s_opp.pop();// 排出已经处理过的操作符
s_opp.push(str[i]);
}else
s_opp.push(str[i]);
}
}
}
while(!s_opp.empty() && s_opp.top() != '\0'){
char ch = s_opp.top();
int x = s_num.top();
s_num.pop();
int y = s_num.top();
s_num.pop();
if(ch == '+')
x += y ;
else
x *= y ;
s_num.push(x);
s_opp.pop();
}
return s_num.top();
}
int main(){
cin >> str;
cout << cal();
return 0;
}
上面这个代码其实是有问题的,不知为何处理不了操作数栈的栈底元素呢?还请大佬们多多指教!
等小良搞清楚了,再来更新本篇博客!
更新啦!更新啦!
上面中缀表达式求值的问题,小良同学后面写了一篇博客,是正确的代码,欢迎大家评论:
栈的综合应用:包括栈混洗和中缀表达式求值(全链式存储!)
更多推荐


所有评论(0)