栈
后进先出(Last In First Out, LIFO)的线性序列,称为“栈”。栈也是一种线性表,只不过它是操作受限的线性表,只能在一端进出操作。进出的一端称为栈顶(top),另一端称为栈底(base)。
栈可以用顺序存储,也可以用链式存储,分别称为顺序栈和链栈。
顺序栈
顺序栈的存储方式
顺序栈需要两个指针,base指向栈底,top指向栈顶。
顺序栈的结构体定义(动态分配的形式)
栈定义好了之后,还要先定义一个最大的分配空间,顺序结构都是如此,需要预先分配空间,因此可以采用宏定义。
上面的结构体定义采用了动态分配的形式,也可以采用静态分配的形式
顺序栈的结构体定义(静态分配的形式)
使用一个定长数组存储数据元素,一个整型下标记录栈顶元素的位置。静态分配的顺序栈结构体定义如图所示
注意:栈只能在一端操作,后进先出,是人为规定的,也就是说不允许在中间查找、取值、插入、删除等操作。顺序栈本身是顺序存储的,有人就想:我偏要从中间取一个元素,不行吗?那肯定可以,但是这样做,就不是栈了。
下面讲解顺序栈的初始化、入栈,出栈,取栈顶元素等基本操作。顺序栈采用动态存储形式,元素以int类型为例
顺序栈的初始化
初始化一个空栈,动态分配Maxsize大小的空间,用S.top和S.base指向该空间的基地址
bool InitStack(SqStack &S)
{
S.base = new int[Maxsize];
if(!S.base)
{
return false;
}
S.top = S.base;
return true;
}
入栈
入栈前要判断是否栈满,如果栈已满,则入栈失败;否则将元素放入栈顶,栈顶指针向上移动一个位置(top++)
算法图解
bool Push(SqStack &S, int e)
{
if(S.top - S.base == Maxsize)
{
return false;
}
*S.top++ = e; //等价于*S.top = e;S.top++;
return true;
}
出栈
出栈前要判断是否栈空,如果栈是空的,则出栈失败;否则将栈顶元素暂存给一个变量,栈顶指针向下移动一个位置(top–)。
算法图解
bool Pop(SqStack &S, int &e)
{
if(S.base == S.top)
{
return false;
}
e = *--S.top;
return true;
}
取栈顶元素
取栈顶元素和出栈不同。取栈顶元素只是把栈顶元素复制一份,栈顶指针未移动,栈内元素个数未变。而出栈是指栈顶指针向下移动一个位置,栈内不再包含这个元素。
算法图解
例如,如图所示,取栈顶元素*(S.top-1),即元素4,取值后S.top指针没有改变,栈内元素的个数也没有改变。
int GetTop(SqStack S)
{
if(S.top != S.base)
{
return *(S.top - 1);
}
else
{
return -1;
}
}
顺序栈我也要玩一玩
#include<bits/stdc++.h>
using namespace std;
#define Maxsize 10
typedef int ElemType;
typedef struct SqStack{
ElemType *base;
ElemType *top;
// int stacksize;
}SqStack;
bool InitStack(SqStack &S)
{
S.base = new int[Maxsize];
if(!S.base)
{
return false;
}
S.top = S.base;
// S.stacksize = Maxsize;
return true;
}
bool Push(SqStack &S, int e)
{
if(S.top - S.base == Maxsize)
{
return false;
}
*S.top++ = e; //等价于*S.top = e;S.top++;
return true;
}
bool Pop(SqStack &S, int &e)
{
if(S.base == S.top)
{
return false;
}
e = *--S.top;
return true;
}
int GetTop(SqStack S)
{
if(S.top != S.base)
{
return *(S.top - 1);
}
else
{
return -1;
}
}
int main()
{
SqStack sq;
InitStack(sq);
for(int i = 0; i < 6; i++)
{
Push(sq, i);
}
int tmp;
Pop(sq, tmp);
cout << tmp << endl;
cout << GetTop(sq) << endl;
return 0;
}
链栈
顺序栈是分配一段连续的空间,需要两个指针:base指向栈底,top指向栈顶。而链栈每个节点的地址是不连续的,只需要一个栈顶指针即可。
链栈的每个节点都包含两个域:数据域和指针域。是不是和单链表一模一样?可以把链栈看作一个不带头节点的单链表,但只能在头部进行插入、删除、取值等操作,不可以在中间和尾部操作。
链栈的结构体定义
链栈的节点定义和单链表一样,只不过它只能在栈顶那一端操作而已。下面讲解链栈的初始化、入栈、出栈、取栈顶元素等基本操作(元素以int类型为例)。
链栈的初始化
初始化一个空的链栈是不需要头节点的,因此只需要让栈顶指针为空即可
bool InitStack(LinkStack &S)
{
S = NULL;
return true;
}
入栈
入栈是将新元素节点压入栈顶。因为链栈中第一个节点为栈顶,因此将新元素节点插到第一个节点的前面,然后修改栈顶指针指向新节点即可。有点像摞盘子,将新节点摞到栈顶之上,新节点成为新的栈顶。
算法图解
bool Push(LinkStack &S, int e)
{
LinkStack p;
p = new Snode;
p->data = e;
p->next = S;
S = p;
return true;
}
出栈
出栈就是把栈顶元素删除,让栈顶指针指向下一个节点,然后释放该节点空间,如图所示。
赋值解释
① p=S:将S的地址赋值给p,即p指向栈顶元素节点。
② S=S->next:将S的后继节点的地址赋值给S,即S指向它的后继节点。
③ delete p:最后释放p指向的节点空间,即delete p。
bool Pop(LinkStack &S, int &e)
{
LinkStack p;
if(S == NULL)
{
return false;
}
e = S->data;
p = S;
S = S->next;
delete p;
return true;
}
取栈顶元素
取栈顶元素和出栈不同,取栈顶元素只是把栈顶元素复制一份,栈顶指针并没有改变。而出栈是指删除栈顶元素,栈顶指针指向了下一个元素。
int GetTop(LinkStack S)
{
if(S != NULL)
{
return S->data;
}
else
{
return -1;
}
}
顺序栈和链栈的所有基本操作都只需要常数时间,所以在时间效率上难分伯仲。
在空间效率方面,顺序栈需要预先分配固定长度的空间,有可能造成空间浪费或溢出;链栈每次只分配一个节点,除非没有内存,否则不会出现溢出,但是每个节点需要一个指针域,结构性开销增加。
因此,如果元素个数变化较大,可以采用链栈;反之,可以采用顺序栈。在实际应用中,顺序栈比链栈应用更广泛。
代码敲起来,链栈随便玩儿
#include<bits/stdc++.h>
using namespace std;
typedef int ElemType;
typedef struct Snode{
ElemType data;
struct Snode *next;
}Snode, *LinkStack;
bool InitStack(LinkStack &S)
{
S = NULL;
return true;
}
bool Push(LinkStack &S, int e)
{
LinkStack p;
p = new Snode;
p->data = e;
p->next = S;
S = p;
return true;
}
bool Pop(LinkStack &S, int &e)
{
LinkStack p;
if(S == NULL)
{
return false;
}
e = S->data;
p = S;
S = S->next;
delete p;
return true;
}
int GetTop(LinkStack S)
{
if(S != NULL)
{
return S->data;
}
else
{
return -1;
}
}
int main()
{
LinkStack S;
InitStack(S);
for(int i = 0; i < 6; i++)
{
Push(S, i);
}
int tmp;
Pop(S, tmp);
cout << tmp << endl;
cout << GetTop(S) << endl;
return 0;
}