栈
目录
一、栈的逻辑结构
1. 栈的概念
后进先出或先进后出的线性结构,最晚到达栈的节点最先被删除。
以取放乒乓球为例:
2. 相关概念
- 栈底(bottom): 结构的首部(节点最早到达的部分)
- 栈顶(top): 结构的尾部(节点最晚到达的地方)
- 出栈(Pop): 节点从栈顶删除
- 进栈(Push): 节点在栈顶位置插入
- 空栈: 栈中节点个数为0的状态
3. 栈的运算
- create():创建一个空的栈
- isEmpty():判断栈是否为空。若栈为空,返回ture,否则返回false
- top():读栈顶元素,返回栈顶元素值
- push(x):进栈,将x插入使其成为栈顶元素
- pop():出栈,删除栈顶元素并返回栈顶元素值
由上述待实现的运算,可以构造出栈的抽象类
template<class elemType>
class stack {
public:
virtual bool isEmpty()const = 0;
virtual elemType Top()const = 0;
virtual void push(const elemType& x) = 0;
virtual elemType pop() = 0;
virtual ~stack() {};
};
二、 栈的物理结构
1.栈的顺序结构
- 用连续的空间存储栈中的节点,即数组
存储映像图如下:
顺序栈类定义如下:
template<class elemType>
class seqStack :public stack<elemType> {
private:
elemType* elem;//数组名
int top;//栈顶
int maxSize;//最大尺寸
void doubleSpace();
public:
seqStack(int initSize = 10);
~seqStack();
bool isEmpty()const;
elemType Top()const;
void push(const elemType& x);
elemType pop();
};
2. 栈的链式结构
- 用单链表存储元素,栈的操作都是在栈顶位置进行的,没有其他位置情况,故不需要头结点。
- 只需要考虑栈顶元素的插入删除
存储映像图如下:
链式栈类定义如下:
template<class elemType>
class linkStack {
private:
struct node {
elemType data;
node* next;
node(const elemType& x, node* N = NULL) {
data = x; next = N;
}
node():next(NULL){}
};
node* Top;
public:
linkStack();
~linkStack();
bool isEmpty()const;
elemType top()const;//一定要注意top T的大小写问题
void push(const elemType& x);
elemType pop();
};
三、栈类的实现
1. 栈的顺序结构实现
实现代码
- 构造类、析构类
//构造
template<class elemType>
seqStack<elemType>::seqStack(int initSize) {
elem = new elemType[initSize];
if (!elem) throw illegalSize();//未能成功生成数组
maxSize = initSize;
top = -1;//栈顶的下标,若有一个元素,则top=0
}
template<class elemType>
seqStack<elemType>::~seqStack() {
delete[]elem;
}
- 属性类
//判断是否为空
template<class elemType>
bool seqStack<elemType>::isEmpty() const{
return(top == -1);//若top=-1,则说明数组内无元素
}
//读取栈顶元素
template<class elemType>
elemType seqStack<elemType>::Top()const {
if (top == -1)throw outOfBound;
return(elem[top]);
}
- 操纵类
//使数组空间加倍
template<class elemType>
void seqStack<elemType>::doubleSpace() {
elemType* tmp = elem;
elem = new elemType[2 * maxSize];
for (int i = 0; i < maxSize; ++i) {
elem[i]=tmp[i];
}
maxSize *= 2;
delete[]tmp;
}
//出栈
template<class elemType>
elemType seqStack<elemType>::pop() {
if (top == -1) throw outOfBound();
return elem[top--];
}
//进栈
template<class elemType>
void seqStack<elemType>::push(const elemType& x) {
if (top == maxSize - 1)doubleSpace();
elem[++top] = x;
}
性能分析
- 除了进栈操作外,所有运算的时间复杂度均为 O ( 1 ) O(1) O(1)
- 进栈最坏情况下时间复杂度为
O
(
N
)
O(N)
O(N)
注:最坏情况在n次进栈中最多出现一次。如果按均摊分析法来说,插入运算还是常量级别 O ( 1 ) O(1) O(1)的时间复杂度。
2.栈的链式结构实现
实现代码
- 1.构造、析构类
//构造函数
template<class elemType>
linkStack<elemType>::linkStack() {
Top = NULL;
}
//析构函数
template<class elemType>
linkStack<elemType>::~linkStack() {
//释放每个节点
node* tmp;
while (Top != NULL) {
//兄弟协同法
tmp = Top;
Top = Top->next;
delete tmp;
}
}
- 属性类
//判断是否为空
template<class elemType>
bool linkStack<elemType>::isEmpty() const{
return Top == NULL;
}
//返回栈顶的值
template<class elemType>
elemType linkStack<elemType>::top() const {
if (Top == NULL) throw outOfBound();
return Top->data;
}
- 操纵类
template<class elemType>
elemType linkStack<elemType>::pop() {
if (Top == NULL)throw outOfBound();
node* tmp = Top;
elemType x = Top->data;
Top = Top->next;
delete tmp;
return x;
}
template<class elemType>
void linkStack<elemType>::push(const elemType& x) {
node*tmp =new node(x, Top);
Top = tmp;
}
性能分析
- 所有操作都是对栈顶的操作,和栈中元素个数无关
- 所有运算时间复杂度为 O ( 1 ) O(1) O(1)
四、栈的应用
1. 栈类的测试
代码总结
- seqstack.h
#include<iostream>
using namespace std;
class illegalSize{};
class outOfBound{};
template<class elemType>
class stack {
public:
virtual bool isEmpty()const = 0;
virtual elemType Top()const = 0;
virtual void push(const elemType& x) = 0;
virtual elemType pop() = 0;
virtual ~stack() {};
};
template<class elemType>
class seqStack :public stack<elemType> {
private:
elemType* elem;//数组名
int top;//栈顶
int maxSize;//最大尺寸
void doubleSpace();
public:
seqStack(int initSize = 10);
~seqStack();
bool isEmpty()const;
elemType Top()const;//注意T大写,否则会与数据成员top产生重定义
void push(const elemType& x);
elemType pop();
};
template<class elemType>
seqStack<elemType>::seqStack(int initSize) {
elem = new elemType[initSize];
if (!elem) throw illegalSize();//未能成功生成数组
maxSize = initSize;
top = -1;//栈顶的下标,若有一个元素,则top=0
}
template<class elemType>
seqStack<elemType>::~seqStack() {
delete[]elem;
}
template<class elemType>
bool seqStack<elemType>::isEmpty() const{
return(top == -1);//若top=-1,则说明数组内无元素
}
template<class elemType>
elemType seqStack<elemType>::Top() const {
if (top == -1)throw outOfBound();
return(elem[top]);
}
template<class elemType>
void seqStack<elemType>::doubleSpace() {
elemType* tmp = elem;
elem = new elemType[2 * maxSize];
for (int i = 0; i < maxSize; ++i) {
elem[i]=tmp[i];
}
maxSize *= 2;
delete[]tmp;
}
template<class elemType>
elemType seqStack<elemType>::pop() {
if (top == -1) throw outOfBound();
return elem[top--];
}
template<class elemType>
void seqStack<elemType>::push(const elemType& x) {
if (top == maxSize - 1) doubleSpace();
elem[++top] = x;
}
- linkstack.h
# include<iostream>
using namespace std;
class outOfBound {};
template<class elemType>
class stack {
public:
virtual bool isEmpty()const = 0;
virtual elemType Top()const = 0;
virtual void push(const elemType& x) = 0;
virtual elemType pop() = 0;
virtual ~stack() {};
};
template<class elemType>
class linkStack {
private:
struct node {
elemType data;
node* next;
node(const elemType& x, node* N = NULL) {
data = x; next = N;
}
node():next(NULL){}
};
node* Top;
public:
linkStack();
~linkStack();
bool isEmpty()const;
elemType top()const;//一定要注意top T的大小写问题
void push(const elemType& x);
elemType pop();
};
template<class elemType>
linkStack<elemType>::linkStack() {
Top = NULL;
}
template<class elemType>
linkStack<elemType>::~linkStack() {
//释放每个节点
node* tmp;
while (Top != NULL) {
//兄弟协同法
tmp = Top;
Top = Top->next;
delete tmp;
}
}
template<class elemType>
bool linkStack<elemType>::isEmpty() const{
return Top == NULL;
}
template<class elemType>
elemType linkStack<elemType>::top() const {
if (Top == NULL) throw outOfBound();
return Top->data;
}
template<class elemType>
elemType linkStack<elemType>::pop() {
if (Top == NULL)throw outOfBound();
node* tmp = Top;
elemType x = Top->data;
Top = Top->next;
delete tmp;
return x;
}
template<class elemType>
void linkStack<elemType>::push(const elemType& x) {
node*tmp =new node(x, Top);
Top = tmp;
}
- seqstack 测试main函数
//文件名:seqstack.cpp
#include<iostream>
#include"seqstack.h"
using namespace std;
int main() {
seqStack<int> s(10);
for (int i = 14; i >= 0; --i) s.push(i);
while (!s.isEmpty()) {
cout << s.Top() << " ";
s.pop();
}
cout << endl;
return 0;
}
- linkstack 测试main函数
#include<iostream>
#include"linkstack.h"
using namespace std;
int main() {
linkStack<int> s;
for (int i = 14; i >= 0; --i) s.push(i);
while (!s.isEmpty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
代码运行结果
在进栈时为倒序,出栈是输出为正序。
2. 递归函数的非递归实现
递归程序的本质是函数调用,而函数调用会花费额外的空间和时间。在系统内部,函数调用是用栈来实现,如果程序员可以自己控制这个栈,就可以消除递归调用。
例如:
n
!
n!
n!的计算
//递归实现
int f1(int n) {
if (n < 0)return 0;
if (n == 0 || n == 1)return 1;
return n * f1(n - 1);
}
//利用栈来实现
int f2(int n)
{
seqStack<int>s;
int m, sum;
sum = 1;
while (n != 0){
s.push(n);
n--;
}
while (!s.isEmpty) {
m = s.pop();
sum = sum * m;
}
return sum;
}
3. 符号平衡检查
见开符号进栈,见闭括号出栈,比较开闭括号是否匹配,结束时判断栈是否为空。
代码如下:
#include <iostream>
#include "linkstack.h"
using namespace std;
bool parenMatch(const char* str) {
linkStack<char> s;
while (*str != '\0') {
switch (*str)
{
case'(':
s.push(*str); break;
case')':
if (s.isEmpty()) {
cout << "句中缺少开括号" << endl;
return false;
}
s.pop();
break;
}
str++;
}
if (!s.isEmpty()) {//式子读入完毕,发现栈中还有多余的开括号
cout << "句中缺少闭括号" << endl;
return false;
}
return true;
}
//测试程序
int main() {
char* str;
char str1[20];
cout << "请输入字符串:" << endl;
cin.get(str1, 20);
str = str1;
bool flag = parenMatch(str);
if (flag) cout << "没有问题" << endl;
else cout << "有问题" << endl;
return 0;
}
运行结果(要注意输入的括号为英文字符):
4. 表达式计算
对于一个表达式 a + b a+b a+b:
- 前缀式(波兰式): + a b +ab +ab
- 中缀式 : a + b a+b a+b
- 后缀式(逆波兰式): a b + ab+ ab+
例如计算 3 ∗ ( 5 + 2 ) 3*(5+2) 3∗(5+2):
- 前缀式:
∗
3
+
5
‾
2
‾
*3+\underline{5} \ \underline{2}
∗3+5 2
当遇到“操作符 数字 数字 ”的组合时则进行运算
需要进行模式判断和多次扫描 - 中缀式: 3 ∗ ( 5 + 2 ) 3*(5+2) 3∗(5+2)
- 后缀式:
3
5
‾
2
‾
+
∗
3\ \underline5 \ \underline2 +*
3 5 2+∗
当遇到“数字 数字 操作符”的组合时则进行运算
不需要模式判断,且只需要一次扫描
尤其注意在进行计算时,运算符的运算顺序并没有发生改变
计算后缀式的思路:
- 初始化一个栈
- 依次读入后缀式的操作数和运算符
- 若遇到的是操作数,则将其进栈
- 若遇到的是运算符,则将栈顶的两个元素出栈,并进行运算,得到的结果进栈
- 回到读入操作,继续进行
- 当后缀式读完,栈中只剩一个操作数,弹出该操作数即为表达式的值
来举一道OJ上的题:
如:3(5–2)+7对应的后缀表达式为:3.5.2.-7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。
注:仅包含-、+、、/运算,其中/是整除。操作数均是int范围内正整数且计算过程中不会超过int范围 。
样例输入
3.5.2.-*7.+@
样例输出
16
代码清单:
#include <iostream>
#include "linkstack.h"
using namespace std;
int calcPost(char* sufStr) {
int op1, op2, op;//op储存运算结果
int tmp=0, i;
linkStack<int>s;
i = 0;
while (sufStr[i] != '@') {
if (sufStr[i] >= '0' && sufStr[i] <= '9') {
//考虑位数问题进行对运算数的处理
tmp = sufStr[i] - '0';
i = i + 1;
while (sufStr[i] != '.') {
tmp = tmp * 10 + sufStr[i] - '0';
i++;
}
s.push(tmp);
}
else {
//弹出运算数
op1 = s.top();
s.pop();
op2 = s.top();
s.pop();
switch (sufStr[i])
{
case'+':
op = op1 + op2;
break;
case'-':
op = op2 - op1;
break;
case'*':
op = op2 * op1;
break;
case'/':
op = op2 / op1;
break;
}
s.push(op);
}
i++;
}
op = s.pop();//最终结果
return op;
}
int main() {
char* str;
char str1[10000];
cin.getline(str1, 10000);
str = str1;
int result = calcPost(str);
cout << result << endl;
return 0;
}
5. 中缀式转后缀式
在计算过程中如何实现将中缀式转化为后缀式呢,我们来寻找一下中缀式和后缀式之间的关系:
中缀式:
5
∗
(
7
−
2
∗
3
)
+
8
/
2
5*(7-2*3)+8/2
5∗(7−2∗3)+8/2
后缀式:
5
‾
7
‾
2
‾
3
‾
∗
−
∗
8
‾
2
‾
/
+
\underline{5} \ \underline7 \ \underline2 \ \underline3*-*\ \underline8 \ \underline2/+
5 7 2 3∗−∗ 8 2/+
总结:操作数顺序不变,操作符和优先级、括号有关,先计算的先出来
思路分析:
(呜呜┭┮﹏┭┮,码字已经码不清了)
总结:
- 栈存放操作符,在栈底压入一个‘#“,其运算优先级最低
- 若读到数字则直接输出
- 若见到左括号则直接进栈,右括号找左括号
- 其余运算符看优先级决定是否进栈,若优先级比栈顶元素低,则进栈,反之则压栈。
- 最终读到’\0’则弹栈直至栈空
题目: 将一个中缀式转化为上面4的后缀式形式
实现代码如下:
#include<iostream>
#include"linkstack.h"
using namespace std;
void inToSufForm(char* inStr, char* sufStr) {
linkStack<char>s;
s.push('#');//#压栈
char topCh;
int i = 0,j = 0;
while (inStr[i] != '\0') {
if (inStr[i] >= '0' && inStr[i] <= '9') {
while(inStr[i]!='.')
sufStr[j++] = inStr[i++];
sufStr[j++] = '.';
}
else{
switch (inStr[i])
{
case '(':
s.push(inStr[i]); break;
case ')'://弹栈,弹出元素进入后缀式,直到弹出一个左括号
topCh = s.pop();
while (topCh != '(') {
sufStr[j++] = topCh;
topCh = s.pop();
}
break;
case '*':
case '/':
topCh = s.top();
while (topCh == '*' || topCh == '/') {
s.pop();
sufStr[j++] = topCh;
topCh = s.top();
}
s.push(inStr[i]);
break;
case '+':
case '-':
topCh = s.top();
while (topCh != '(' && topCh != '#') {
s.pop();
sufStr[j++] = topCh;
topCh = s.top();
}
s.push(inStr[i]);
break;
}//switch
i++;
}//else
}//while
//将栈中还没弹出的操作符弹出
topCh = s.top();
while (topCh != '#') {
sufStr[j++] = topCh;
s.pop();
topCh = s.top();
}
sufStr[j] = '@';
sufStr[j+1] = '\0';
}
int main() {
char* str_1,*str_2;
char str1[100];
char str2[100];
cin.getline(str1, 100);
str_1 = str1;
str_2 = str2;
inToSufForm(str_1, str_2);
int i = 0;
while (str_2[i]!='\0') {
cout << char(str_2[i++]);
}
return 0;
}
运行结果为:
将4、5两个part结合,计算一个中缀式的值:
main函数调用如下:
int main() {
char* str_1,*str_2;
char str1[100];
char str2[100];
cout << "请输入中缀表达式:" << endl;
cin.getline(str1, 100);
str_1 = str1;
str_2 = str2;
inToSufForm(str_1, str_2);
int i = 0;
cout << "后缀表达式形式为:" << endl;
while (str_2[i]!='\0') {
cout << char(str_2[i++]);
}
cout << endl;
cout << "计算结果为:" << endl;
cout << calcPost(str_2) << endl;
return 0;
}
输出结果为:
五、总结
终于算是暂时完结了,貌似也是第一次敲这么长的文章。
栈在现实生活中可能并不是应用很广(可能没队列常见),但是在计算机系统中却具有独特的优势和广泛的应用。
在敲代码的过程中也出现了不少的bug,但总体上栈的操作还是比较容易理解的。在应用上,代码的可更改空间也留了不少,改个计算结束标志啊等等,应该还是比较容易滴。
看看接下来的树能不能建起来呢(些许担心…)