前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据结构资料汇编:栈

数据结构资料汇编:栈

作者头像
老齐
发布2023-03-02 16:53:16
4620
发布2023-03-02 16:53:16
举报
文章被收录于专栏:老齐教室老齐教室

定义

栈(stack)是限定仅在表尾进行插入或删除操作的线性表。

  • 表尾称为栈顶(top),可以进行插入或删除操作
    • 栈的插入操作称为进栈入栈(push)
    • 栈的删除操作成为出栈退栈(pop)
  • 表头称为栈底(bottom)
  • 不含元素的空表称为空栈
  • 操作特性:后进先出(Last In First Out,LIFO)

抽象数据类型

代码语言:javascript
复制
ADT Stack {
    数据对象: D = {a_i | a_i in ElemSet, i=1,2,...,n, n<=0}
    数据关系: R = {<a_{i-1}, a_i> | a_{i-1}, a_i in D, i=2,...,n},约定 a_n 端为栈顶,a_1 端为栈底。
    基本操作:
        InitStack(&S)
            操作结果:构造一个空栈 S
        DestroyStack(&S)
            初始条件:栈 S 已存在
            操作结果:栈 S 被销毁
        ClearStack(&S)
            初始条件:栈 S 已存在
            操作结果:将栈 S 清为空栈
        StackEmpty(S)
            初始条件:栈 S 已存在
            操作结果:若栈 S 为空栈,则返回 true,否则返回 false。
        StackLength(S)
            初始条件:栈 S 已存在
            操作结果:返回 S 的元素个数,即栈的长度。
        GetTop(S)
            初始条件:栈 S 已存在且非空
            操作结果:返回 S 的栈顶元素,不修改栈顶指针。
        Push(&S, e)
            初始条件:栈 S 已存在
            操作结果:插入元素 e 为新的栈顶元素。
        Pop(&S, &e)
            初始条件:栈 S 已存在且非空。
            操作结果:删除 S 的栈顶元素,并用 e 返回其值。
        StackTraverse(S)
            初始条件:栈 S 已存在且非空。
            操作结果:从栈底到栈顶依次对 S 的每个数据元素进行访问
}ADT Stack

栈的存储结构:

  • 顺序栈:利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针 top 指示栈顶元素在顺序表中的位置。
  • 链栈:指采用链式存储结构实现的栈。通常用单链表表示。

顺序栈的存储结构

  1. 参考资料 [1] 中关于顺序栈的定义:
代码语言:javascript
复制
# define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef struct {
    SElemType *base;  //栈底指针
    SElemType *top;   //栈顶指针
    int stacksize;    //栈可用的最大容量
}SqStack;
  • base 为栈底指针,初始化完成后,栈底指针 base 始终指向栈底的位置(栈底元素的下边沿),若 base 的值为 NULL ,则表明栈结构不存在。
  • top 为栈顶指针,其初始值指向栈底。每当掺入新的栈顶元素时,指针 top 增加 1;删除栈顶元素时,指针 top 减 1。因此,栈空时,topbase 的值相等,都指向栈底;栈非空时,top 始终指向栈顶。
  • stacksize 指示栈可使用的最大容量。
  • 注意区分,参考资料 [1] 中以下表述:
    • 栈顶元素:线性表的最后一个的元素;
    • 栈底元素:线性表的第一个元素;
    • 栈顶:线性表的表尾,即最后一个元素的“后边”。
    • 栈底:线性表的表头,即第一个元素的“前边”。
  1. 参考资料 [2] 中关于顺序栈的定义:
代码语言:javascript
复制
# define MaxSize 100 //顺序栈存储空间的初始分配量
typedef struct
{
    ElemType data[MaxSize]; //存放栈中数据元素
    int top;  //栈顶指针,即存放栈顶元素在 data 数组中的下标
}SqStack;

设置顺序栈 s 的栈顶指针初始值 s->top = -1 ,则:

  • 栈空的条件:s->top == -1
  • 栈满的条件:s->top == MaxSize - 1 (即 data 数组的最大下标)
  • 元素 e 进栈:先将栈顶指针 top 增 1,然后将元素 e 放在栈顶指针处
  • 出栈操作:先将栈顶指针处的元素取出放在 e 中,然后将栈顶指针减 1

说明:

以上两种定义,本质上是一样的。在第一种定义中,栈 s 的指针 s.base 在对栈进行操作时不变化,因此可以用它指向栈的数据元素。

另外,两种定义内的 s.top 起始值不同,在插入和删除的时候,会有所差异。

顺序栈的基本操作

顺序栈的初始化

使用参考资料 [1] 中的顺序栈数据结构:

代码语言:javascript
复制
//参考资料 [1] 的算法3.1
Status InitStack(SqStack &S){//构造一个空栈 S
    S.base = new SElemType[MAXSIZE];//为顺序栈动态分配一个最大容量为 MAXSIZE 的数组空间
    //S.base=(SElemType *)malloc(MAXSIZE*sizeof(SElemType));
    if(!S.base) exit(OVERFLOW);//存储分配失败
    S.top = S.base; //top 初始值为 base,空栈
    S.stacksize = MAXSIZE;//stacksize 设置为栈的最大容量 MAXSIZE
    return OK;
}

使用参考资料 [2] 中的顺序栈数据结构:

代码语言:javascript
复制
void InitStack(SqStack * &s){
    s = (SqStack *)malloc(sizeof(SqStack));  //分配一个顺序栈空间,首地址存放在 s 中
    s->top = -1;   //栈顶指针置为 -1
}
顺序栈的入栈
代码语言:javascript
复制
//参考资料 [1] 的算法3.2
Status Push(SqStack &S, SElemType e){//插入元素 e 为新的栈顶元素
    if(S.top - S.base == S.stacksize) retuan ERROR; //栈满
    ((*S).top)++ =  e;//元素 e 压入栈顶,栈顶指针加 1。注意顺序,先赋值,再加 1
    return OK;
}

注意此处入栈的操作顺序,先完成 (*S).top = e 赋值,再执行 (*S).top++ 指针加 1。这是由参考资料 [1] 所确定的栈的数据结构决定的,即初始化时:S.top = S.base ,且 S.base 指向数组的第一个,即索引为 0 。

如果使用参考资料 [2] 的数据结构,即初始化时:s.top = -1 (该书中写作 s->top = -1),则入栈的操作是(参考资料 [2] 的 81 页):

代码语言:javascript
复制
bool Push(SqStack * &s, ElemType e){
    if (s->top == MaxSize-1) return false;  //栈满的情况,即栈上溢出
    s->top++;   //栈顶指针增 1
    s->data[s->top] = e;   //元素 e 放在栈顶指针处
    return true;
}
顺序栈的出栈
代码语言:javascript
复制
//参考资料 [1] 的算法3.3
Status Pop(SqStack &S, SElemType &e){//删除 S 栈顶元素,用 e 返回其值
    if (S.top == S.base) return ERROR;  //栈空
    e = *--S.top;  //栈顶指针减1, 将栈顶元素赋值给 e
    return OK;
}
代码语言:javascript
复制
//参考资料 [2] 的算法
bool Pop(SqStack * &s, ElemType &e){
    if (s->top == -1) return false;  //栈为空的情况,即栈下溢出
    e = s->data[s->top];   //取栈顶元素
    s->top--;    //栈顶指针减 1
    return true;
}
销毁顺序栈
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
void DestroyStack(SqStack *S)
{ /* 销毁栈S,S不再存在 */
 free((*S).base);
 (*S).base=NULL;
 (*S).top=NULL;
 (*S).stacksize=0;
}
代码语言:javascript
复制
//针对 [2] 的栈结构
void DestroyStack(SqStack * s){
    free(s);
}
判断顺序栈是否为空
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
Status StackEmpty(SqStack S)
{ /* 若栈S为空栈,则返回TRUE,否则返回FALSE */
 if(S.top == S.base)
  return TRUE;
 else
  return FALSE;
}
代码语言:javascript
复制
//针对 [2] 的栈结构
bool StackEmpty(SqStack * s){
    return (s->top == -1);
}
读取顺序栈的栈顶元素
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
Status GetTop(SqStack S,SElemType *e)
{ /* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
 if(S.top > S.base)
 {
  *e=*(S.top-1);
  return OK;
 }
 else
  return ERROR;
}
代码语言:javascript
复制
//针对 [2] 的栈结构
bool GetTop(SqStack * s, ElemType &e){
    if (s->top = -1) return false;  //栈为空,即栈下溢出
    e = s->data[s->top];   //取栈顶元素
    return true
}
清空顺序栈
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
void ClearStack(SqStack *S)
{ /* 把S置为空栈 */
 (*S).top=(*S).base;
}
顺序栈的长度
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
int StackLength(SqStack S)
{ /* 返回S的元素个数,即栈的长度 */
 return S.top-S.base;
}
依次访问顺序栈所有元素
代码语言:javascript
复制
//针对 [1] 的栈结构,来自 [3]
void StackTraverse(SqStack S, void(*visit)(SElemType))
{ /* 从栈底到栈顶依次对栈中每个元素调用函数visit() */
 while(S.top > S.base)
  visit(*S.base++);
 printf("\n");
}

链栈的存储结构

参考资料 [1] 中关于链栈的定义:

代码语言:javascript
复制
//链栈的存储结构
typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
}StackNode, *LinkStack;
  • 以链表的头部作为栈顶
  • 不附加头结点

参考资料 [2] 中关于链栈的定义:

代码语言:javascript
复制
typedef struct linknode{
    ElemType data;   //数据域
    struct linknode * next;   //指针域
}LinkStNode;
  • 采用带头结点的单链表实现链栈。
  • 链栈的优点:不存在栈满上溢出情况。
  • 规定所有操作都在单链表的表头进行。首结点是栈顶结点,尾结点是栈底结点。
  • 栈空的条件:s->next == NULL
  • 栈满的条件:由于只有内存溢出时才出现栈满,通过长不考虑此情况,所以在链栈中可以看成不存在栈满
  • 元素 e 进栈操作:新建一个结点存放元素 e (由 p 指向它),将结点 p 插入到头结点之后
  • 出栈操作:取出首结点的 data 值并将其删除。

链栈的基本操作

链栈的初始化
代码语言:javascript
复制
//参考资料 [1] 的算法3.5
Status InitStack(LinkStack &S){//构造一个空栈 S,栈顶指针为置空
    S = NULL;
    return OK;
}
代码语言:javascript
复制
//参考资料 [2]
void InitStack(LinkStNode * &s){
    s = (LinkStNode *)malloc(sizeof(LinkStNode));
    s->next = NULL;
}

时间复杂度:

O(1)
链栈的入栈
代码语言:javascript
复制
//参考资料 [1] 的算法3.6
//此处的单链表不带头结点
Status Push(LinkStack &S, SElemType e){//在栈顶插入元素 e
    p = new StackNode;  //生成新结点
    p->data = e;   // 将新结点数据域置为 e
    p->next = S;  //将新结点插入栈顶
    S = p;  //修改栈顶指针为 p
    return OK;
}
代码语言:javascript
复制
//参考资料 [2]
//注意,在这里用带头结点的单链表
vodi Push(LinkStNode * &s, ElemType e){
    LinkStNode *p;
    p = (LinkStNode *)malloc(sizeof(LinkStNode));  //新建节点 p
    p->data = e;  //存放元素 e
    p->next = s->next; //将 p 结点插入作为首元结点
    s->next = p; 
}

时间复杂度:

O(1)
链栈的出栈
代码语言:javascript
复制
//参考资料 [1] 的算法3.7
Status Pop(LinkStack &S, SElemType &e){//删除 S 的栈顶元素,用 e 返回其值
    if (S == NULL) return ERROR;  //栈空
    e = S->data;  //将栈顶元素赋给 e
    p = S;   //用 p 临时保存栈顶元素空间,以备释放
    S = S->next;    //修改栈顶指针
    delete p;    //释放原栈顶元素空间
    return OK;
}
代码语言:javascript
复制
//参考资料 [2]
bool Pop(LinkStNode * &s, ElemType &e){
    LinkStNode * p;
    if(s->next == NULL) return false;  //栈空的情况
    p = s->next;  // p 指向首元结点
    e = p->data;  //提取首元结点的值
    s->next = p->next;  //删除首元结点
    free(p);   //释放被删除的结点存储空间
    return true;
}

时间复杂度:

O(1)
取链栈的栈顶元素
代码语言:javascript
复制
//参考资料 [1] 的算法3.8
SElemType GetTop(LinkStack S){//返回 S 的栈顶元素,不修改栈顶指针
    if (S != NULL){//栈非空
        return S->data;  //返回栈顶元素的值,栈顶指针不变
    }
}
代码语言:javascript
复制
//参考资料 [2]
bool GetTop(LinkStNode * s, ElemType &e){
    if(s->next != NULL) return false;  //栈空
    e = s->next->data;
    return true;
}

时间复杂度:

O(1)
销毁链栈
代码语言:javascript
复制
//参考资料 [2]
void DestroyStack(LinkStNode * &s){
    LinkStNode *pre = s, *p = s->next; //pre指向头结点,p指向首元结点
    while(p != NULL){
        free(pre); //释放 pre 结点
        pre = p;
        p = pre->next;  //pre,p同步后移
    }
    free(pre);  //此时 pre 指向尾结点,释放其空间
}

时间复杂度:

O(n)
判断链栈是否为空
代码语言:javascript
复制
//参考资料 [2]
bool StackEmpty(LinkStNode * s){
    return (s->next == NULL);
}

与判断单链表是否为空的方法一样。

代码语言:javascript
复制
//参考资料 [3] ,不带头结点的单链表
Status ListEmpty(LinkList L)
{ /* 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
 if(L)
  return FALSE;
 else
  return TRUE;
}

参考资料

[1]. 严蔚敏等. 数据结构:C语言版[M]. 北京:人民邮电出版社,2015.

[2]. 李春葆. 数据结构教程. 北京:清华大学出版社,2017.

[3]. 维基百科:栈[EB/OL]. https://zh.wikipedia.org/wiki/%E5%A0%86%E6%A0%88 , 2023.2.2

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 抽象数据类型
  • 栈的存储结构:
    • 顺序栈的存储结构
      • 顺序栈的基本操作
        • 顺序栈的初始化
        • 顺序栈的入栈
        • 顺序栈的出栈
        • 销毁顺序栈
        • 判断顺序栈是否为空
        • 读取顺序栈的栈顶元素
        • 清空顺序栈
        • 顺序栈的长度
        • 依次访问顺序栈所有元素
      • 链栈的存储结构
        • 链栈的基本操作
          • 链栈的初始化
          • 链栈的入栈
          • 链栈的出栈
          • 取链栈的栈顶元素
          • 销毁链栈
          • 判断链栈是否为空
      • 参考资料
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档