目录
一、栈的基本概念
1.定义:只允许在一端进行插入或删除操作的线性表。
2.相关术语:
(1)空栈:没有存入数据的栈;
(2)栈顶:允许插入和删除的一端,栈顶的元素称为栈顶元素;
(3)栈底:不允许插入和删除的一端,栈底的元素称为栈底元素;
3.栈的一个重要特点就是:先进后出,后进先出,Last In First Out(LIFO);
4.栈的逻辑结构和线性表相同,数据运算上:插入删除操作有所区别。
5.栈的基本操作:(所有时间复杂度都是O(1))
(1)InitStack(&S):初始化栈,构造一个空栈S,分配内存空间;
(2)DestroyStack(&S):销毁栈,销毁并释放栈S所占用的内存空间;
(3)Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶;
(4)Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回;
(5)GetTop(S,&x):读栈顶元素,若栈S非空,则用x返回栈顶元素;
(6)StackEmpty(S):判断一个栈S是否为空,若S为空,则返回true,否则返回false。
6.n个不同元素进栈,出栈元素不同排列的个数为: (卡特兰数)。
二、栈的顺序存储实现
1.代码定义顺序栈
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct
{
int data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针,名叫指针,实则只是一个int型整数来反映栈顶元素的数组下标
}SqStack; //后续声明一个顺序栈(分配空间)就 SqStack S;
补充解释:
(1)顺序存储:给各个数据元素分配连续的存储空间,大小为MaxSize*sizeof(ElemType);
(2)结构中定义的int top名为栈顶指针,这里的“指针”和我们所说的数据类型指针实际上不是一回事。这里的栈顶指针我们也可以看到其数据类型为int型整数,但为什么把它叫做指针呢?是因为在顺序栈的实现中我们采用了静态数组的存储方式来存放元素(int data[MaxSize]),所以这里的栈顶指针实则是映射到栈顶元素的数组下标,为了形象化它的功能,我们形象地称其“指针”;
(3)由于数组下标是从0开始的,所以top的值是元素个数-1;比如栈中按顺序存入了a(data[0])、b(data[1])、c(data[2])、d(data[3])、e(data[4])这么5个元素,则top=4。
2.顺序栈的初始化与判空
(1)由于最开始栈里面是没有元素存入的,那栈顶也没有元素,所以top不能指向0的位置,初始化时我们可以让top指向-1即可,如下:
void InitStack(SqStack& S)
{
S.top = -1;
}
(2)基于此,我们便可以给出顺序栈的判空条件,即判断top是否指向-1,如下:
bool StackEmpty(SqStack S)
{
if (S.top == -1)
return true;
else
return false;
}
(3)注意以上两个函数,初始化需要对顺序栈做出修改,定义形参时注意打上&;而判空函数则不需要。
3.进栈操作
(1)Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶;代码如下:
bool Push(SqStack& S, int x)
{
if (S.top == MaxSize - 1) //栈满,报错
return false;
S.top = S.top + 1; //A 指针先+1
S.data[S.top] = x; //B 新元素入栈
return true;
}
(2)对S内容修改,形参打&;x不需要返回则不用引用;
(3)注释中A句先对栈顶指针+1,使栈顶指针指向新的位置;B句将x写入top指向新位置的数据域;基于此操作顺序,我们可以将AB合为一句:S.data[++S.top]=x; 因为是先对top进行+1,再赋值,所以写为++S.top,如果写成S.top++则导致AB两句顺序颠倒。
4.出栈操作
(1)Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回;代码如下:
bool Pop(SqStack& S, int &x)
{
if (S.top == -1) //栈空,报错
return false;
x = S.data[S.top]; //A 栈顶元素先出栈
S.top = S.top - 1; //B 指针再-1
return true;
}
(2)对S内容修改,形参打&;x需要返回也需要打&;
(3)同入栈操作的注释一样,出栈操作注释中的AB两句可简写为:x=S.data[S.top--];
5.读取栈顶元素
(1)GetTop(S,&x):读栈顶元素,若栈S非空,则用x返回栈顶元素;代码如下:
bool GetTop(SqStack S, int& x)
{
if (S.top == -1) //栈空,报错
return false;
x = S.data[S.top]; //x记录栈顶元素
return true;
}
6.附另一种实现方法:
在初始化时将top指向0的位置,即S.top=0;此时增删等操作需改为:
(1)进栈:S.data[S.top++]=x;
(2)出栈:x=S.data[--S.top];
(3)栈满条件为:top==MaxSize;
7.共享栈
顺序栈的缺点是其容量大小不可改变,可采用“链式栈”来解决这个问题;或者先申请较大的一片内存,采用“共享栈”解决(两个栈共享同一片空间):
#define MaxSize 10
typedef struct
{
int data[MaxSize]; //静态数组存放栈中元素
int top0; //0号栈栈顶指针
int top1; //1号栈栈顶指针
}ShStack;
void InitStack(ShStack& S)
{
S.top0 = -1; //0号栈从下往上依次存放
S.top1 = MaxSize; //1号栈从上往下依次存放
}
补充解释:
(1)共享栈的思路是:申请较大的存储空间,为了避免浪费,将两个顺序栈都存放在这一片存储空间中;
(2)在结构定义时定义两个“栈顶指针”,初始化时一个指向-1,一个指向MaxSize,指向-1的栈顶指针控制其中一个栈从下往上进行存储,另一个从上往下进行存储;
(3)逻辑上实现了两个不同的栈,但在物理上它们又是共享同一片存储空间,进而提高内存利用率;
(4)判断共享栈栈满的条件是:top0+1==top1。
三、栈的链式存储实现
1.代码实现链栈的定义:
typedef struct LinkNode
{
int data;
struct LinkNode* next;
}*LiStack;
2.链栈的进栈和出栈和链表的插入删除操作类似,只不过只能在栈顶一端进行(把链头作为栈顶);链栈也分为带头结点或不带头结点两个版本。
3.链栈的初始化
(1)带头结点
bool InitLiStack(LiStack &S)
{
S = (LinkNode*)malloc(sizeof(LinkNode));
if (S == NULL)
return false;
S->next = NULL;
return true;
}
(2)无头结点
bool InitListack(LiStack& S)
{
S = NULL;
return true;
}
4.链栈的判空
(1)带头结点
bool EmptyLiStack(LiStack S)
{
if (S->next == NULL)
return true;
else
return false;
}
(2)无头结点
bool EmptyListack(LiStack S)
{
return (S == NULL);
}
5.进栈
(1)带头结点
bool LSPush(LiStack& S, int x)
{
LiStack m = (LinkNode*)malloc(sizeof(LinkNode));
if (m == NULL) //申请失败,报错
return false;
m->data = x; //x赋值给m的数据域
m->next = S->next; //将m连向头结点的后继结点
S->next = m; //将头结点的后继结点改为m
return true;
}
(2)无头结点
bool LsPush(LiStack& S, int x)
{
LiStack m= (LinkNode*)malloc(sizeof(LinkNode));
if (m == NULL) //申请失败,报错
return false;
m->data = x; //x赋值给m的数据域
m->next = S->next; //将m连向头地址所指的NULL
S->next = m; //修改头地址所指的结点
return true;
}
6.出栈
(1)带头结点
bool LSPop(LiStack& S,int x)
{
if (S->next == NULL) //空栈,无法删除
return false;
LinkNode* p=S->next; //定义p指向头结点的后继结点
x = p->data; //p的数据域赋值给x
S->next = p->next; //将头结点指向p的后继结点
p->next = NULL; //将p的指针域置空
free(p); //释放p
return true;
}
(2)无头结点
bool LsPop(LiStack& S, int x)
{
if (S == NULL) //空栈,无法删除
return false;
LinkNode* p = S->next; //定义p指向头指针所指的第一个结点
x = p->data; //p的数据域赋值给x
S = p->next; //将头指针指向p的后继结点
p->next = NULL; //将p的指针域置空
free(p); //释放p
return true;
}
7.获取栈顶元素
(1)带头结点
bool GetTop(LiStack S, int& x)
{
if (S->next = NULL) //空栈,无法获取
return false;
LinkNode* p = S->next; //定义p指向头结点的后继结点
x = p->data; //p的数据域赋值给x
return true;
}
(2)无头结点
bool Gettop(LiStack S, int& x)
{
if (S == NULL) //空栈,无法获取
return false;
LinkNode* p = S->next; //定义p指向头指针所指的第一个结点
x = p->data; //p的数据域赋值给x
return true;
}