**栈(stack)是限定仅在表的一端进行插入和删除操作的线性表,允许插入和删除的一端称为栈顶(stack top),另一端称为栈底(stack bottom),不含任何元素的栈称为空栈,栈还具有后进先出(last in first out)**的特性。
顺序栈
栈的顺序存储结构称为顺序栈(sequential stack),顺序栈本质上是顺序表的简化,唯一需要确定的是用数组的哪一端表示栈底,通常把数组中下标为0的一端称为栈底,同时附设变量top指示栈顶元素在数组中的位置。
顺序栈的实现
const int StackSize = 10; // 根据实际问题具体定义
template <typename DataType>
class SeqStack{
public:
SeqStack(); // 构造函数,初始化一个空栈
~SeqStack(){} // 析构函数
void Push(DataType x); // 入栈操作,将元素x入栈
DataType Pop(); // 出栈操作,将栈顶元素弹出
DataType GetTop(); // 取栈顶元素(并不删除)
bool Empty(); // 判断栈是否为空
private:
DataType data[StackSize]; // 存放栈元素的数组
int top; // 栈顶元素在数组中的下标
};
template <typename DataType>
SeqStack<DataType>::SeqStack(){
top = -1;
}
template <typename DataType>
void SeqStack<DataType>::Push(DataType x){
if (top == StackSize-1)
throw "overflow";
data[++top] = x;
}
template <typename DataType>
DataType SeqStack<DataType>::Pop(){
if (top == -1)
throw "underflow";
return data[top--];
}
template <typename DataType>
DataType SeqStack<DataType>::GetTop(){
if (top == -1)
throw "no element in stack";
return data[top];
}
template <typename DataType>
bool SeqStack<DataType>::Empty(){
return top==-1?true:false;
}
链栈
栈的链接存储结构称为链栈(linked stack),通常用单链表表示,因为只能在栈顶执行插入和删除操作,显然以单链表的头部作为栈顶是最方便的,而且没有必要像单链表那样为了运算方便附加头结点。
链栈的实现
template <typename DataType>
struct Node{
DataType data;
Node<DataType> *next;
};
template <typename DataType>
class LinkStack{
public:
LinkStack(); // 构造函数,初始化一个空链栈
~LinkStack(); // 析构函数,释放链栈各结点的存储空间
void Push(DataType x); // 入栈操作,将元素x入栈
DataType Pop(); // 出栈操作,将栈顶元素出栈
DataType GetTop(); // 取栈顶元素(并不删除)
bool Empty(); // 判空操作,判断链栈是否为空栈
private:
Node<DataType> *top; // 栈顶指针即链栈的头指针
};
template <typename DataType>
LinkStack<DataType>::LinkStack(){
top = nullptr;
}
template <typename DataType>
LinkStack<DataType>::~LinkStack(){
Node<DataType> *p = top;
while (top != nullptr){
top = top->next;
delete p;
p = top;
}
}
template <typename DataType>
void LinkStack<DataType>::Push(DataType x){
Node<DataType> *p = new Node<DataType>; p->data = x;
p->next = top;
top = p;
}
template <typename DataType>
DataType LinkStack<DataType>::Pop(){
if (top == nullptr)
throw "underflow";
Node<DataType> *p = top;
DataType x = p->data;
top = top->next;
delete p;
return x;
}
template <typename DataType>
DataType LinkStack<DataType>::GetTop(){
if (top == nullptr)
throw "no element in stack";
return top->data;
}
template <typename DataType>
bool LinkStack<DataType>::Empty(){
return top==nullptr?true:false;
}
双栈共享空间
在一个程序中,如果同时使用具有相同数据类型的两个顺序栈,最直接的方法是为每个栈开辟一个数组空间,这样做的结果可能会出现一个栈的空间被占满而无法进行插入操作,同时另一个栈空间仍有大量剩余而没有得到利用的情况,从而造成存储空间的浪费。
可以充分利用顺序栈单向延展的特性,使得一个数组来存储两个栈,让一个栈的栈底位于该数组的始端,另一个栈的栈底位于该数组的末端,每个栈从各自的端点向中间延伸,其中,top1和top2分别为栈1和栈2的栈顶位置,StackSize为整个数组空间的大小,栈1的底位于下表为0的一端;栈2的底位于下标为StackSize-1的一端。
在双栈共享空间中,由于两栈相向增长,浪费的数组空间就会减少,同时发生上溢的概率也会减少。但是,只有当两个栈的空间需求有相反的关系时,这种方法才会奏效,也就是说,最好一个栈增加时另一个栈缩短。
双栈的实现
const int StackSize = 100; // 根据具体问题定义
template <typename DataType>
class BothStack{
public:
BothStack(); // 构造函数,将两个栈分别初始化
~BothStack(){} // 析构函数
void Push(int i,DataType x); // 入栈操作,将元素x压入栈i
DataType Pop(int i); // 出栈操作,对栈i执行出栈操作
DataType GetTop(int i); // 取栈i的栈顶元素
bool Empty(int i); // 判断栈i是否为空栈
private:
DataType data[StackSize]; // 存放两个栈的数组
int top1,top2; // 两个栈的栈顶指针,分别为各自栈顶元素在数组中的下标
};
template <typename DataType>
BothStack<DataType>::BothStack(){
top1 = -1;
top2 = StackSize;
}
template <typename DataType>
void BothStack<DataType>::Push(int i,DataType x){
if (top1 == top2-1)
throw "overflow";
if (i == 1)
data[++top1] = x;
if (i == 2)
data[--top2] = x;
}
template <typename DataType>
DataType BothStack<DataType>::Pop(int i){
if (i == 1){
if (top1 == -1)
throw "underflow";
return data[top1--];
}
if (i == 2){
if (top2 == StackSize)
throw "underflow";
return data[top2++];
}
}
template <typename DataType>
DataType BothStack<DataType>::GetTop(int i){
if (i == 1){
if (top1 == -1)
throw "stack 1 is empty";
return data[top1];
}
if (i == 2){
if (top2 == StackSize)
throw "stack 2 is empty";
return data[top2];
}
}
template <typename DataType>
bool BothStack<DataType>::Empty(int i){
if (i == 1)
return top1==-1?true:false;
if (i == 2)
return top2==StackSize?true:false;
}
顺序栈和链栈的比较
顺序栈和链栈基本操作的时间复杂度均为O(1),因此唯一可以比较的是空间性能。初始时顺序栈必须确定一个固定的长度,所以有存储元素个数的限制和浪费空间的问题。而链栈没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。
作为一般规律,当栈的使用过程中元素个数变化较大时,应该采用链栈,反之,应该采用顺序栈。