【数据结构与算法】第五章:栈的表示与操作实现

简介: 本章将开启新的内容:栈与队列。栈和队列是两种重要的线性结构。从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表,因此,可称为限定性的数据结构。本章将简单介绍栈与队列的定义及栈的表示和实现。

 

🙊🙊作者主页:🔗求不脱发的博客

📔📔 精选专栏:🔗数据结构与算法

📋📋 精彩摘要:本章将开启新的内容:栈与队列。栈和队列是两种重要的线性结构。从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表,因此,可称为限定性的数据结构。本章将简单介绍栈与队列的定义及栈的表示和实现。

💞💞觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞


📚目录

📖【数据结构与算法】第五章:栈的表示与操作实现

📝1️⃣栈和队列的定义和特点

📝2️⃣栈的表示和操作的实现

📝3️⃣顺序栈的表示和实现

📝4️⃣栈的表示和实现


📖【数据结构与算法】第五章:栈的表示与操作实现


📝1️⃣栈和队列的定义和特点

✨栈的定义和特点

栈(stack)是限定仅在表尾进行插入或删除操作的线性表。因此,对栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。

image.gif编辑

在日常生活中,还有很多类似栈的例子。例如,洗干净的盘子总是逐个往上叠放在已经洗好的盘子上面,而用时从上往下逐个取用。栈的操作特点正是上述实际应用的抽象。在程序设计中,如果需要按照保存数据时相反的顺序来使用数据,则可以利用栈来实现。

定义: 只能在表的一端(栈顶)进行插入和删除运算的线性表。
逻辑结构: 与线性表相同,仍为一对一关系。
存储结构: 用顺序栈或链栈存储均可,但以顺序栈更常见。
运算规则: 只能在栈顶运算,且访问结点;是依照后进后出或先进后出的原则。
实现方式: 关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同。
基本操作: 入栈、出栈、读取栈顶元素。建栈、判断栈是否满、栈空等。

✨队列的定义和特点

和栈相反,队列(queue)是一种先进先出(First In First Out,FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。

image.gif编辑

这和日常生活中的排队是一致的,最早进入队列的元素最早离开

在队列中,允许插入的一端称为队尾(rear),允许删除的一端则称为队头(front)。假设队列为q = (a1, a2,…, an),那么,a1就是队头元素,an则是队尾元素。

队列中的元素是按照a1, a2,…, an的顺序进入的,退出队列也只能按照这个次序依次退出,也就是说,只有在a1, a2,…, an − 1都离开队列之后,an才能退出队列。

定义: 只能在表的一端(队尾)进行插入,在另一端(队头)删除运算的线性表。
逻辑结构: 与线性表相同,仍为一对一关系。
存储结构: 用顺序队或链队存储均可。
运算规则: 先进先出。
实现方式: 关键是编写入队和出队函数,具体实现依顺序队或链队的不同而不同。
基本操作: 入队、出队、读取队首元素、站队、判断队是否满、队空等。

✨栈和队列与一般线性表的区别

栈、队列是一种特殊(操作受限)的线性表,区别在于运算规则不同。

区别 \ 结构 一般线性表 队列
逻辑结构: 一对一 一对一 一对一
存储结构: 顺序表、链表 顺序栈、链栈 顺序队、链队
运算规则: 随机、顺序存取 后进先出 先进先出

📝2️⃣栈的表示和操作的实现

栈的基本操作除了入栈和出栈外,还有栈的初始化、栈空的判定,以及取栈顶元素等。

✨栈的抽象数据类型定义

ADT Stack{
    数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
    数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,…,n}
    约定an端为栈顶,a1端为栈底。
//基本操作:
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

image.gif


📝3️⃣顺序栈的表示和实现

image.gif编辑

✨类型定义

#define MAXSIZE 100
typedef struct
{
    SElemType *base;
    SElemType *top;
    int stacksize;
}SqStack;

image.gif

✨初始化

顺序栈的初始化操作就是为顺序栈动态分配一个预定义大小的数组空间。

【算法步骤】

    1. 为顺序栈动态分配一个最大容量为MAXSIZE的数组空间,使base指向这段空间的基地址,即栈底。
    2. 栈顶指针top初始为base,表示栈为空。
    3. stacksize置为栈的最大容量MAXSIZE

    【算法描述】

    Status InitStack(SqStack &S)
    {    //构造一个空栈S
        S.base=new SElemType[MAXSIZE]; //为顺序栈动态分配一个最大容量为MAXSIZE的数组空间
        if(!S.base) 
            exit(OVERFLOW); //存储分配失败
        S.top=S.base; //top初始为base,空栈
        S.stacksize=MAXSIZE; //stacksize置为栈的最大容量MAXSIZE
        return OK;
    }

    image.gif

    ✨入栈

    入栈操作是指在栈顶插入一个新的元素。

    【算法步骤】

      1. 判断栈是否满,若满则返回ERROR。
      2. 将新元素压入栈顶,栈顶指针加1。

      【算法描述】

      Status Push(SqStack &S,SElemType e)
      {    //插入元素e为新的栈顶元素
          if(S.top-S.base==S.stacksize) 
              return ERROR; //栈满
          *S.top++=e; //元素e压入栈顶,栈顶指针加1
          return OK;
      }

      image.gif

      ✨出栈

      出栈操作是将栈顶元素删除。

      【算法步骤】

        1. 判断栈是否空,若空则返回ERROR。
        2. 栈顶指针减1,栈顶元素出栈。

        【算法描述】

        Status Pop(SqStack &S,SElemType &e)
        {    //删除S的栈顶元素,用e返回其值
            if(S.top==S.base)
                return ERROR; //栈空
            e=*--S.top; //栈顶指针减1,将栈顶元素赋给e
            return OK;
        }

        image.gif

        ✨取栈顶元素

        当栈非空时,此操作返回当前栈顶元素的值,栈顶指针保持不变。

        【算法描述】

        SElemType GetTop(SqStack S)
        {    //返回S的栈顶元素,不修改栈顶指针
            if(S.top!=S.base) //栈非空
                return *(S.top-1); //返回栈顶元素的值,栈顶指针不变
        }

        image.gif

        由于顺序栈和顺序表一样,受到最大空间容量的限制,虽然可以在“满员”时重新分配空间扩大容量,但工作量较大,应该尽量避免。因此在应用程序无法预先估计栈可能达到的最大容量时,还是应该使用下面介绍的链栈。


        📝4️⃣栈的表示和实现

        链栈是指采用链式存储结构实现的栈。通常链栈用单链表来表示,如图3.4所示。链栈的结点结构与单链表的结构相同,在此用StackNode表示

        image.gif编辑

        ✨类型定义

        typedef struct StackNode
        {
            SElemType data;
            stuct StackNode *next;
        }StackNode,*LinkStack;
        LinkStack S;

        image.gif

        由于栈的主要操作是在栈顶插入和删除,显然以链表的头部作为栈顶是最方便的,而且没必要像单链表那样为了操作方便附加一个头结点。

        ✨初始化

        链栈的初始化操作就是构造一个空栈,因为没必要设头结点,所以直接将栈顶指针置空即可。

        【算法描述】

        Status InitStack(LinkStack &S)
        {    //构造一个空栈S,栈顶指针置空
            S=NULL;
            return OK;
        }

        image.gif

        ✨入栈

        和顺序栈的入栈操作不同的是,链栈在入栈前不需要判断栈是否满,只需要为入栈元素动态分配一个结点空间。

        【算法步骤】:

          1. 为入栈元素e分配空间,用指针p指向。
          2. 将新结点数据域置为e。
          3. 将新结点插入栈顶。
          4. 修改栈顶指针为p。

          【算法描述】:

          Status Push(LinkStack &S, SElemType e)
          {    //在栈顶插入元素e
              p=new StackNode; //生成新结点
              p->data=e; //将新结点数据域置为e
              p->next=S; //将新结点插入栈顶
              S=p; //修改栈顶指针为p
              return OK;
          }

          image.gif

          ✨出栈

          和顺序栈一样,链栈在出栈前也需要判断栈是否为空,不同的是,链栈在出栈后需要释放出栈元素的栈顶空间。

          【算法步骤】:

            1. 判断栈是否为空,若空则返回ERROR。
            2. 将栈顶元素赋给e。
            3. 临时保存栈顶元素的空间,以备释放。
            4. 修改栈顶指针,指向新的栈顶元素。
            5. 释放原栈顶元素的空间。

            【算法描述】:

            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;
            }

            image.gif

            ✨取栈顶元素

            与顺序栈一样,当栈非空时,此操作返回当前栈顶元素的值,栈顶指针S保持不变。

            【算法描述】:

            SElemType GetTop(LinkStack S)
            {    //返回S的栈顶元素,不修改栈顶指针
                if(S!=NULL) //栈非空
                    return S->data; //返回栈顶元素的值,栈顶指针不变
            }

            image.gif

            相关文章
            |
            1月前
            |
            算法 C语言 C++
            【practise】栈的压入和弹出序列
            【practise】栈的压入和弹出序列
            |
            9天前
            |
            存储 人工智能 C语言
            数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
            本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
            |
            11天前
            |
            存储 C语言
            数据结构基础详解(C语言): 栈与队列的详解附完整代码
            栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
            |
            12天前
            |
            Java
            【数据结构】栈和队列的深度探索,从实现到应用详解
            本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
            14 0
            【数据结构】栈和队列的深度探索,从实现到应用详解
            |
            1月前
            栈的几个经典应用,真的绝了
            文章总结了栈的几个经典应用场景,包括使用两个栈来实现队列的功能以及利用栈进行对称匹配,并通过LeetCode上的题目示例展示了栈在实际问题中的应用。
            栈的几个经典应用,真的绝了
            |
            16天前
            |
            Linux C++ Windows
            栈对象返回的问题 RVO / NRVO
            具名返回值优化((Name)Return Value Optimization,(N)RVO)是一种优化机制,在函数返回对象时,通过减少临时对象的构造、复制构造及析构调用次数来降低开销。在C++中,通过直接在返回位置构造对象并利用隐藏参数传递地址,可避免不必要的复制操作。然而,Windows和Linux上的RVO与NRVO实现有所不同,且接收栈对象的方式也会影响优化效果。
            |
            1月前
            |
            负载均衡 网络协议 安全
            DKDP用户态协议栈-kni
            DKDP用户态协议栈-kni
            |
            1月前
            |
            存储 安全 编译器
            缓冲区溢出之栈溢出(Stack Overflow
            【8月更文挑战第18天】
            55 3
            |
            1月前
            |
            负载均衡 网络协议 安全
            DPDK用户态协议栈-KNI
            DPDK用户态协议栈-KNI
            |
            1月前
            |
            测试技术
            【初阶数据结构篇】栈的实现(附源码)
            在每一个方法的第一排都使用assert宏来判断ps是否为空(避免使用时传入空指针,后续解引用都会报错)。