stack 栈是一种LIFO(后进先出last in first out)的数据结构, 它在stl 函数中是通过其它容器,如队列实现的,stack不能使用iterator迭代器,在首部的声明是#include<stack>, 一般c++自己的头文件都不带.h。
stack的常用个函数包括了,push(),pop(),top(),empty(),swap() (为空时返回true),size()(返回栈中元素的个数)。注意写法上与vector的区别,vector是 push_back() ,pop_back()。
stack 的拷贝操作为 stack<int> s2(s1),实现了s2对s1的拷贝操作。swap不是拷贝而是进行了互换的操作。
需要注意的是,pop()函数不带参数,返回类型时void,即不访问栈顶元素。
需要注意的栈不能随机访问元素,只能访问逐一栈顶的元素,还要逐一 s.size() 可能会出现动态变化随着pop或者push的操作。
下面是栈的基本操作:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<stack>
using namespace std;
int main()
{
stack<int> s;
s.push(1);
s.push(2);
int a[] = { 3,4,5 };
s.push(a[1]);
stack<int> s2(s);
printf("stack<int> s2(s)拷贝后得到s2从栈顶开始元素为: ");
int sz = s2.size();
//重点,下面的sz不能用s2.size()代替,因为s2.size()随着弹出栈将会发生变化
for (int i = 0; i < sz; i++)
{
printf("%d ",s2.top());
s2.pop();
}
printf("\n");
printf("元素全部弹出后, s2.size(): %d",s2.size());
printf("\n");
//swap()操作
s.swap(s2);
printf("执行s.swap(s2)后,s2.size()= %d, s2.top()=%d,", s2.size(), s2.top());
printf("\n");
swap(s,s2);
printf("执行swap(s,s2)操作后,s2.size()= %d, s.size()= %d",s2.size(),s.size());
printf("\n");
printf("按照栈顶开始打印此时栈s中的所有元素,并逐一弹出\n");
printf("%d\n",s.top());
s.pop();
printf("%d\n",s.top());
s.pop();
printf("%d\n",s.top());
s.pop();
if (s.empty())
printf("已经弹完全部栈中元素!\n");
return 0;
}
运行结果为:
stack<int> s2(s)拷贝后得到s2从栈顶开始元素为: 4 2 1
元素全部弹出后, s2.size(): 0
执行s.swap(s2)后,s2.size()= 3, s2.top()=4,
执行swap(s,s2)操作后,s2.size()= 0, s.size()= 3
按照栈顶开始打印此时栈s中的所有元素,并逐一弹出
4
2
1
已经弹完全部栈中元素!
请按任意键继续. . .
括号匹配问题:
括号匹配是经典的栈问题,先来看一个例子:
题目描述
在某个字符串(长度不超过 100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用"$"标注,不能匹配的右括号用"?"标注.
输入:
输入包括多组数据,每组数据一行,包含一个字符串,只包含左右括号和大小写字母,字符串长度不超过 100。
输出:
对每组输出数据,输出两行,第一行包含原始输入字符,第二行由"$","?"和空格组成,"$"和"?"表示与之对应的左括号和右括号不能匹配。
样例输入:
)(rttyy())sss)(
样例输出:
)(rttyy())sss)(
? ?$
代码:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<stack>
using namespace std;
char str[200];
char ans[200];
int main()
{
stack<char> S;
while (scanf("%s", str) != EOF)
{
//用str[i]!= 0或者‘\0’或者NULL 都一样,真值都是0
int i;
for (i = 0; str[i] != 0; i++)
{
if (str[i] == '(')
{
//压入栈的是位置,这与前面的那种做法是不同的
//因为只有一种括号类型,所以不用记类型,只要记住有没有和位置即可
S.push(i);
ans[i] = ' ';//暂时设为空格
}
if (str[i] == ')')
{
if (!S.empty())
{
S.pop();
ans[i] = ' ';
}
else
ans[i] = '?';
}
else
ans[i] = ' ';
}
while (!S.empty())
{
ans[S.top()] = '$';
S.pop();
}
//重要,要将ans数组改为字符串形式,尾部要加上一个0,或者是'\0'
ans[i] = 0;
puts(str);
puts(ans);
char ch = 0;
//说明0和\0都是一个空字符,记住 \0不能写在双引号当中
printf("ch = %c",ch);
}
return 0;
}
需要注意的是这一题中的Stack 最后得到了清空,ans[] 虽然没有得到清空,但每次都是新的赋值,最后加上一个 空0 ,打印的都是真正的结果,所以多组数据不相互影响,ans[] 和 stack 都不用初始化。但是下面的题目就要注意这点,要将两个栈全部清空后才能继续操作。
运行结果:
)(rttyy())sss)(
)(rttyy())sss)(
? ?$
ch =
gets(str)函数只有遇到换行符时才会结束,而scanf(str)则在遇到空格,tab,以及换行符时都会结束,而puts()会在结束时自动换行,都在stdio.h中定义,vs2015中不支持gets(),用gets_s来代替,char 到 int 可以用强制转换的方法,不能用atoi (字符串到整型的变换,在stdio.h中定义),itoa函数则实现从整型的字符串的变换。
再来看一个解决运算式优先级的例子:
题目描述:
题目描述:
读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值。
输入:
测试输入包含若干测试用例,每个测试用例占一行,每行不超过 200 个字符,整数和运算符之间用一个空格分隔。没有非法表达式。当一行中只有 0 时输入结 束,相应的结果不要输出。
输出:
对每个测试用例输出 1 行,即该表达式的值,精确到小数点后 2 位。
样例输入:
1+2
4 + 2 * 5 - 7 / 11
0
样例输出:
3.00
13.36
题目很容易理解,要注意的几点是: 每个数字,运算符之间有一个空格相连,数字可能为多位数,输出是 double型的,要注意类型的转换,要设置的两个栈分别为: int 型 (存储运算符对应运算符优先级数组的下标)和 double型(存储运算结果) ,使用 \0 作为标识字符,开始时自己压入op栈中一个,结尾将字符串的结尾压入栈一个。
代码为:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<stack>
using namespace std;
char str[220];
//对于二维数组赋值,可以不用对每一维加括号
int m[][5] =
{
1,0,0,0,0,
1,0,0,0,0,
1,0,0,0,0,
1,1,1,0,0,
1,1,1,0,0,
};
stack<int> op;
stack<double> in;
//输入的都是int型,但是整除计算可能会产生double型的,结果也要求保留2位小数点
//获得表达式中下一个元素的函数,i是当前遍历到的字符串的下标
//reto 为true时,表示该位置是一个运算符,用retn来表示其编号
//reto 为false时,表示该位置是一个数字,用retn来返回其值
//retn 是整数,这是题目的条件
//之所以用到引用的形式,是因为可以直接进行修改,而不用返回或者因为局部变量而被销毁
//引用传递的使用
void getOp(bool &reto,int &retn, int &i)
{
//当遍历到第一个位置时,我们用'\0'来作为人为的标记,下面不用true应该也可以
//用 \0 而不是 其它字符的好处是字符串的结尾也是 \0
if (i == 0 && op.empty() == true)
{
reto = true;
retn = 0;
return;//立即实现返回,返回类型为void时可以不加上return
}
//当字符串被遍历完时,结尾不是换行符,而是空0
if (str[i] == 0)
{
reto = true;
retn = 0;//返回编号也是0
return;
}
if (str[i] >= '0' && str[i] <= '9')
reto = false;
//当为字符时
else
{
reto = true;
if (str[i] == '+')
retn = 1;
else if (str[i] == '-')
retn = 2;
else if (str[i] == '*')
retn = 3;
else if (str[i] == '/')
retn = 4;
i += 2; //跳过后面的空格
return;
}
//当为数字时,上面已经完成了对各种运算符的处理,这里又接上了上面关于数字的处理
retn = 0;//返回结果为数字
//将夹在空格之间或者是空格和字符串尾部的0之间的完整数字进行存储,因为可能不止一个数字
for (; str[i] != ' ' && str[i] != 0; i++)
{
retn *= 10;
//转换为数字
retn += str[i] - '0';
}
if (str[i] == ' ')
i++;
return;
}
int main()
{
//vs2015不支持gets,但可以使用gets_s代替,puts可以照常使用
//这里不能用scanf
while (gets_s(str))
{
//当输入的字符串是一个0时结束
if (str[0] == '0' && str[1] == 0)
break;
bool reto;
int retn;
int idx = 0;//idx的增加是在getOp函数中通过引用传递实现的
//防止上一组数据产生影响
while (!op.empty())
op.pop();
while (!in.empty())
in.pop();
while (true)
{
getOp(reto, retn, idx);
//要转换为double型的在 整除操作时才能是真正的double型结果,这一点可以通过输出两位小数不为0看出
if (reto == false)
in.push((double)retn);
else
{
double tmp;
//当运算符栈为空或者当前运算符优先级大于栈顶运算符优先级或者!!为 标记 \0 时都会被压入栈
//因为只有 \0 的优先级等于自己时有 m[0][0] == 1 ,其余的 + 等符号与自己进行比较时都是 0 ,表示等于
if (op.empty() || m[retn][op.top()] == 1)
{
//为空时retn也为0
op.push(retn);
}
else
{
//当前运算符优先级小于或者等于栈顶运算符优先级时会一直进行循环
while (m[retn][op.top()] == 0)
{
int r = op.top();
op.pop();
double b = in.top();
in.pop();
//先取top,再进行pop()
double a = in.top();
in.pop();
switch (r)
{
case 1:
tmp = a + b;
break;
case 2:
tmp = a - b;
break;
case 3 :
tmp = a * b;
break;
case 4:
tmp = a / b;
break;
}
in.push(tmp);
}
op.push(retn);
}
}
//当只有两个\0(包括字符串结尾的那个 0 )时,结束循环
if (op.size() == 2 && op.top() == 0)
break;
}
printf("%.2lf\n",in.top());
}
return 0;
}
运行结果:
3 + 4
7.00
3 + 4 + 5 - 5 / 2
9.50
4 + 2 * 5 - 7 / 11
13.36