说明
本博客用于记录本人的学习历程,本人水平有限,如有错误,欢迎在评论区指出。
本博客中的代码均由C++实现。
如果你觉得看完这篇文章有收获,就动动小手点个赞吧!
在实际使用中,手写栈会很浪费时间,也容易出现错误。可以使用STL中封装好的stack,方便又快捷,附上stack的使用方法:
https://blog.csdn.net/SDAU_LGX/article/details/104730819
一. 栈的特点
- 具有先进后出、后进先出的特点。
- 栈是一种操作受限的线性表,只允许从一端插入和删除数据,这一端被称为栈顶,另一段被称为栈底。
- 不含任何数据元素的栈被称为空栈。
- 栈有两种存储方式,线性存储和链接存储(链表)。
二、顺序存储
1. 顺序栈
栈的顺序存储结构被称为顺序栈,用数组来储存数据。需要确定的是用数组的那一段来做栈底,一般来说,使用下标为0的位置作为栈底。
顺序栈的实现
使用C++中的类来实现顺序栈,为了使用更加的方便,使用模板机制。
#include<iostream>
using namespace std;
const int MAX_SIZE=1e5;
template<typename T>
class SeqStack {
int data[MAX_SIZE];
int top;
public:
SeqStack();//构造一个空栈
~SeqStack();//析构
void Push(T x);//入栈
void Pop();//出栈
bool Empty();//判断栈是否为空
T GetTop();//返回栈顶元素
};
构造函数
构造一个空栈,将栈顶指针top赋值为-1
template<typename T>
SeqStack<T>::SeqStack()
{
top = -1;
}
析构函数
顺序栈是静态分配内存,析构函数为空即可
template<typename T>
SeqStack<T>::~SeqStack()
{
}
入栈操作
先判断栈是否为满栈,若为满栈则不可插入,抛出异常。不满则栈顶指针top++,插入元素
template<typename T>
void SeqStack<T>::Push(T x)
{
if (top == MAX_SIZE - 1)throw"满栈";
data[++top]=x;
return;
}
出栈操作
先判断是否为空栈,若空栈则不可出栈,抛出异常。不是空栈则top--
template<typename T>
T SeqStack<T>::Pop()
{
T x;
if (top == -1)throw"空栈";
x = data[top--];
return x;
}
判空操作
判断top是否为-1,若为-1则返回true,否则返回false
template<typename T>
bool SeqStack<T>::Empty()
{
if (top == -1)return true;
return false;
}
返回栈顶元素
判断是否为空栈,若为空栈则抛出异常,不是则返回data[top]
template<typename T>
T SeqStack<T>::GetTop()
{
if (top == -1)throw"空栈";
return data[top];
}
2. 双端栈(两栈共享空间)
双端栈就是用一个数组来储存两个栈,能有效地节省空间。让其中一个栈的栈底为数组的首端,另一个栈的栈底为数组的尾端,向中间延伸。
双端栈的实现
声明
const int MAX_SIZE = 1e5;
template<typename T>
class BothStack {
int top1;
int top2;
T data[MAX_SIZE];
public:
BothStack();//建立空栈
~BothStack();//析构函数
void Push(int i, T x);//入栈
T Pop(int i);//出栈
T GetTop(int i);//获取栈顶元素
bool Empty(int i);//判断栈是否为空
};
构造函数
将两个栈顶指针都初始化为-1
template<typename T>
BothStack<T>::BothStack()
{
top1 = -1;
top2 = MAX_SIZE;
}
析构函数
利用数组储存数据,析构函数为空
template<typename T>
BothStack<T>::~BothStack()
{
}
入栈操作
首先判断是否满栈,然后再进行插入
这里注意判断满栈的条件是两个栈指针相邻,即 top1+1=top2
同时还要注意top指针是自加还是自减
以数组首为栈底的栈top1++,以数组尾为栈底的栈top2--
template<typename T>
void BothStack<T>::Push(int i, T x)
{
if (top1 + 1 == top2)throw"满栈";
if (i == 1)data[++top1] = x;
if (i == 2)data[--top2] = x;
}
出栈操作
首先判断是否为空栈(判断要进行出栈操作的栈)
还要注意栈顶指针top的操作
以数组首为栈底的栈top1--,以数组尾为栈底的栈top2++
template<typename T>
T BothStack<T>::Pop(int i)
{
if (i == 1)
{
if (top1 == -1)throw"空栈";
return data[top--];
}
else if (i == 2)
{
if (top2 == MAX_SIZE)throw"空栈";
return data[top++];
}
}
获取栈顶元素
先判断是否为空,再返回栈顶元素就可
template<typename T>
T BothStack<T>::GetTop(int i)
{
if (i == 1)
{
if (top1 == -1)throw"空栈";
return data[top1];
}
else if (i == 2)
{
if (top2 == MAX_SIZE)throw"空栈";
return data[top2];
}
}
判断是否为空
判断top是否为-1
template<typename T>
bool BothStack<T>::Empty(int i)
{
if (i == 1)
{
if (top1 == -1)return true;
return false;
}
else if (i == 2)
{
if (top2 == MAX_SIZE)return true;
return false;
}
}
三、链接存储
4.链栈
栈的链接存储结构为链栈,一般用单链表表示。为了操作方便,以单链表的头部作为栈顶,同时不需要头结点。
链栈的实现
声明
template<typename T>
struct Node {
T data;
Node<T>* next;
};
template<typename T>
class LinkStack {
Node<T>* top;
public:
LinkStack();//构造一个空栈
~LinkStack();//析构
void Push(T x);//入栈
T Pop();//出栈
bool Empty();//判断栈是否为空
T GetTop();//返回栈顶元素
};
构造函数
构造一个不带头结点的单链表,是top指针为空即可
template<typename T>
LinkStack<T>::LinkStack()
{
top = NULL;
}
析构函数
回收申请的内存,和单链表的析构函数类似
template<typename T>
LinkStack<T>::~LinkStack()
{
Node<T>* p = NULL;
while (top)
{
p = top;
top = top->next;
delete p;
}
}
入栈操作
由于是链式存储,所以不用判断是否为满栈
template<typename T>
void LinkStack<T>::Push(T x)
{
Node<T>* s = new Node<T>;
s->data = x;
s->next = top;
top = s;
}
算法描述:
先申请一下新节点,储存数据x
修改新节点的指针域指向top
top指针前移到新建立的结点
出栈操作
先判断栈是否为空,再进行操作
template<typename T>
T LinkStack<T>::Pop()
{
if (top == NULL) throw"空栈";
T x = top->data;
Node<T>* p;
p = top;
top = top->next;
delete p;
return x;
}
算法描述:
先判断栈是否为空
把栈顶元素储存起来
栈顶指针后移
释放栈顶空间
获取栈顶元素
先判断是否为空,不为空则返回栈顶元素
template<typename T>
T LinkStack<T>::GetTop()
{
if (top == NULL)throw"空栈";
return top->data;
}
判断栈是否为空
判断top指针是否为空
template<typename T>
bool LinkStack<T>::Empty()
{
if (top == NULL)return true;
return false;
}
四、顺序栈和链栈的比较
可以发现,顺序栈的链栈的时间复杂度都为O(1),所以我们来比较空间性能。
顺序栈需初始化一个固定的长度,所以会有长度限制和空间浪费的现象。链栈不会出现满栈的情况,但是因为每个元素都需要一个指针域,所以产生了空间的额外开销。
所以当栈使用过程中元素个数变化不大时使用顺序栈,否则使用链栈。