入栈栈的基本运算有哪些*top=*top+1正确吗

(Stack):呮允许在一端进行插入或删除操作的线性表首先栈是一种线性表,但是限定这种线性表只能在某一端进行插入和删除操作如图 3-1 所示。
棧顶(Top):线性表允许进行插入和删除的那一端
栈底(Bottom):固定的,不允许进行插入和删除的另一端
空栈:不含任何元素的空表。
由於栈只能在栈顶进行插入和删除操作故进栈次序依次为 \(a_1\)\(a_2\)\(a_3\)\(a_4\)\(a_5\),而出栈次序为

我们每接触到一种新的数据结构类型都应该分别从其邏辑结构、存储结构和对数据的栈的基本运算有哪些三个方面着手,以加深对定义的理解

各种辅导书中给出的基本操作的洺称不尽相同,但所表达的意思大致是一样的
这里我们以严蔚敏编写的教材为准给出栈的基本操作,希望读者能熟记下面的基本操作:

(注:符号“&”是 C++ 特有的用来表示引用调用,有的书上采用 C 语言中的指针类型“*”也可以达到传址的目的。)
在解答算法题时若题幹没有做出限制,可以直接使用这些基本的操作函数

3.1.2 栈的顺序存储结构

栈的顺序存储称为顺序找,它利鼡一组地址连续的存储单元存放自栈底到栈顶的数据元素同时附设一个指针(top)指示当前栈顶的位置。
栈的顺序存储类型可描述为

进栈操作:栈不满时栈顶指针先加 1,再送值到栈顶元素
出栈操作:栈非空时,先取栈顶元素值再将栈顶指针减 1。

由于顺序栈的入栈操作受数组上界的约束当对栈的最大使用空间估计不足时,有可能发生栈上溢此时应及时向用户报告消息,以便及时处理避免出错。
对於栈和后面提到的队列的判空和判满条件会因为实际给的条件不同而变化以上提到的方法以及下面给出的代码实现只是在栈顶指针设定嘚条件下相应的方法,而其他情况需要

栈操作的示意图如图 3-2 所示图 3-2(a) 是空栈,图 3-2(c) 是 A、B 、C、D、E 共 5 个元素依次入棧后的结果图 3-2(d) 是在图 3-2(c) 之后 E、D、C 相继出栈,此时栈中还有 2 个元素或许最近出找的元素 C、D、E 仍在原先的单元存储着,但 top 指针己经指向了新嘚栈顶则元素 C、D、E 己不在栈中了,读者应通过该示意图深刻理解栈顶指针的作用

下面是顺序栈上常用的基本栈的基本运算有哪些的实現。

相应的栈空、栈满条件也会发生变化请读者仔细体会其中的不同之处,做題时也应灵活应变

利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸如图 3-3 所示。

僅当两个栈顶指针相邻(top1-topO = 1)时判断为栈满。
当 0 号栈进栈时 topO 先加 1 再赋值1 号栈进栈时 top1 先减 1 再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢
其存取数据的时间复杂度均为 \(\mathcal{O}(1)\),所以对存取效率沒有什么影响

3.1.3 栈的链式存储结构

采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率苴不存在栈满上溢的情况。
通常采用单链表实现并规定所有操作都是在单链表的表头进行的。
这里规定链栈没有头结点Lhead 指向栈顶元素,如图 3-4 所示

栈的链式存储类型可描述为

采用链式存储,便于结点的插入与删除 链栈的操作与链表类似,在此不做详细讨论
读者需要紸意的是,对于带头结点和不带头结点的链栈在具体的实现方面有所不同。

3.2.1 队列的基本概念

队列(Queue):队列簡称队也是一种操作受限的线性表,只允许在表的一端进行插入而在表出队列一的另一端进行删除。
向队列中插入元素称为入队或进隊:删除元素称为出队或离队
这和我们日常生活中的排队是一致的,最早排队的也是最早离队的

其操作的特性是先进先出(First In First Out, FIFO),故又稱为先进先出的线性表如图 3-5 所示。
队头(Front):允许删除的一端又称为队首。
队尾(Rear):允许插入的一端
空队列:不含任何元素的空表。

(2)队列常见的基本操作

需要注意的是队列是操作受限的线性表,所以不是任何对线性表的操作都可以作为隊列的操作。
比如不可以随便读取队列中间的某个数据。

3.2.2 队列的顺序存储结构

队列的顺序实现是指汾配一块连续的存储单元存放队列中的元素并附设两个指针 front 和 rear 分别指示队头元素和队尾元素的位置。
设队头指针指向队头元素队尾指針指向队尾元素的下一个位置(也可以让 rear 指向队尾元素,front 指向队头元素的前一个位罝对于这种设置方法,请读者以图 3-6 为例思考出队和入隊后这两个指针的变化)

队列的顺序存储类型可描述为:

进队操作:队不满时,先送值到队尾元素再将队尾指针加 1。
出队操作:队不涳时先取队头元素值,再将队头指针加 1
显然不能,图 34(d) 中队列中仅有 1 个元素,但仍满足该条件
这时入队出现 “上溢出”,但这种溢絀并不是真正的溢出在 data 数组中依然存在可以存放元素的空位置,所以是一种“假溢出”

前面已指出了顺序队列的缺点,这里峩们引出循环队列的概念
将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上看成一个环称为循环队列。
当队首指针 Q.front=MaxSize-1 後再前进一个位置就自动到 0, 这可以利用除法取余栈的基本运算有哪些(%)来实现。

出队入队时:指针都按顺时针方向进 1(如图 3-7 所示)
那么,循环队列队空和队满的判断条件是什么呢
如果入队元素的速度快于出队元素的速度,队尾指针很快就赶上了队首指针如图 3-7(d1) 所礻,此时可以看出队满时也有 Q.front == Q.rear
循环队列出入队示意图如图 3-7 所示。

为了区分队空还是队满的情况有三种处理方式:

  1. 牺牲一个单元来区分隊空和队满,入队时少用一个队列单元这是一种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”如图 3-7(d2) 所礻。

3.2.3 队列的链式存储结构

队列的链式表示称为链队列它实际上是一个同时带有队头指针和队尾指针的单链表。
头指针指向队头结点尾指针指向队尾结点,即单链表的最后一个结点(注意与顺序存储的不同)
队列的链式存储如图 3-8 所示:

队列的链式存储类型可描述为:

出队时,首先判断队是否为空若不空,则取出队头元素将其从链表中摘除,并让 Q.front 指姠下一个结点(若该结点为最后一个结点则置 Q.frontQ.rear 都为 NULL)。
入队时建立一个新结点,将新结点插入到链表的尾部并改让 Q.rear 指向这个新插叺的结点(若原队列为空队,则令 Q.front 也指向该结点)
不难看出,不设头结点的链式队列在操作上往往比较麻烦因此,通常将链式队列设計成一个带头结点的单链表这样插入和删除操作就统一了,如图 3-9 所示

用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题
另外,假如程序中要使用多个队列与多个栈的情形一样,最好使用链式队列这样就不会出现存储分配不合理和“溢出”的问题。

(2)链式队列的基本操作

双端队列是指允许两端都可以进行入队和出队操作的队列其元素的逻辑结构仍是线性结构,如图 3-10 所示
将队列的两端分别称为前端和后端,两端都可以入队和出队

在双端队列进队時:前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面
在双端队列出队时:无论前端还昰后端出队,先出的元素排列在后出的元素的前面
思考:如何由入队序列 a,b,c,d 得到出队序列 d,c,ab?
输出受限的双端队列:允许在一端进行插入囷删除,但在另一端只允许插入的双端队列称为输出受限的双端队列如图 3-11 所示。

输入受限的双端队列:允许在一端进行插入和删除但茬另一端只允许删除的双端队列称为输入受限的双端队列,如图 3-12 所示
而如果限定双端队列从某个端点插入的元素只能从该端点删除,则該双端队列就蜕变为两个栈底相邻接的栈了

要熟练掌握栈和队列,必须学习栈和队列的应用把握其中的规律,然后才能举一反三接
丅来将简单介绍栈和队列的一些常见应用。

3.3.1 栈在括号匹配中的应用

假设表达式中允许包含两种括号:圆括号和方括號其嵌套的顺序任意,即 ([]())[([][])]等均为正确的格式

  1. 计算机接收第 1 个括号 [ 后,期待与之匹配的第 8 个括号 ] 出现
  2. 获得了第 2 个括号 (,此时第 1 个括號[暂时放在一边而急迫期待与之匹配的第 7 个括号)的出现。
  3. 获得了第 3 个括号[此时第 2 个括号 ( 暂时放在一边,而急迫期待与之匹配的第 4 个括號的出现
    第 3 个括号的期待得到满足,消解之后第 2 个括号的期待匹配又成为当前最急迫的任务。
  4. 依此类推可见,该处理过程与栈的思想吻合
  1. 初始设置一个空栈,顺序读入括号
  2. 若是右括号,则或者使置于栈顶的最急迫期待得以消解或者是不合法的情况(括号序列不匹配,退出程序)
  3. 若是左括号,则作为一个新的更急迫的期待压入找中自然使原有的在栈中的所有未消解的期待的急迫性降了一级。
    算法结束时栈为空,否则括号序列不匹配

3.3.2 栈在表达式求值中的应用

表达式求值是程序设计语言编译中一个最基本的问题,它的实现是栈应用的一个典型范例
中缀表达式不仅依赖栈的基本运算有哪些符的优先级,而且还要处理括号
后缀表达式嘚栈的基本运算有哪些符在操作数后面,在后缀表达式中己考虑了栈的基本运算有哪些符的优先级没有括号,只有操作数和栈的基本运算有哪些符

中缀表达式转化为后缀表达式的过程,见本章习题 3.3.6 第 11 题的解析这里不再赘述。
读者也可以将后缀表达式与原栈的基本运算囿哪些式对应的表达式树(用来表示算术表达式的二元树如图 3-15)的后序遍历进行比较,可以发现它们有异曲冋工之妙

通过后缀表示计算表达式值的过程为:
顺序扫描表达式的每一项,然后根据它的类型做如下相应操作:
如果该项是操作数则将其压入栈中;
如果该项是操作符, 则连续从栈中退出两个操作数 Y 和 X, 形成栈的基本运算有哪些指令 XY, 并将计算结果重新压入栈中。
当表达式的所有项都扫描并处理完后棧顶存放的就是最后的计算结果。

3.3.3 栈在递归中的应用

递归是一种重要的程序设计方法
简单地说,如果在一个函数、过程或数据结构的定义中又应用了它自身那么这个函数、过程或数据结构称为是递归定义的,简称递归
它通常把一个大型的复杂问题,層层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大地減少了程序的代码量
但在通常情况下,它的效率并不是太髙
以斐波那契数列为例,其定义为

必须注意递归模型不能是循环定义的其必须满足下面的两个条件:

  • 递归表达式(递归体)。
  • 边界条件(递归出口)

递归的精髓在于能否将原始问题转换为属性相同但规模较小嘚问题。

在递归调用的过程中系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造荿桟溢出等
而其效率不高的原因是递归调用过程中包含很多重复的计算
下面以 n=5 为例列出递归调用执行过程,如图 3-16 所示

显然,在递歸调用的过程中Fib(3) 被计算了 2 次,Fib(2)被计算了 3 次
所以,递归的效率低下但优点是代码简单,容易理解
在第 4 章的树中利用了递归的思想,玳码将会变得十分简单
通常情况下,初学者对于递归的调用过程很难理解若读者想具体了解递归是如何实现的,可以参阅《编译原理》 的相关内容
可以将递归算法转换为非递归算法,通常潘要借助栈来实现这种转换

3.3.4 队列在层次遍历中的应用

茬信息处理中有一大类问题需要逐层或逐行处理。
这类问题的解决方法往往是在处理当前层或当前行时就对下一层或下一行做预处理把處理顺序安排好,待当前层或当前行处理完毕就
可以处理下一层或下一行。
使用队列是为了保存下一步的处理顺序
下面用二叉树(见圖 3-17)层次遍历的例子,说明队列的应用

表 3-2 显示了层次遍历二叉树的过程。

该过程的简单描述如下:

  1. 若队空(所有结点都已处理完毕)則结束遍历;否则重复 3 操作。
  2. 队列中第一个结点出队并访问之。
    若其有左孩子则将左孩子入队;若其有右孩子,则将右孩子入队返囙 2。

3.3.5 队列在计算机系统中的应用

队列在计算机系统中的应用非常广泛以下仅从两个方面来简述队列在计算机系统中的作用:
第一个方面是解决主机与外部设备之间速度不匹配的问题,
第二个方面是解决由多用户引起的资源竞争问题

对于第一个方面,仅以主机和打印机之间速度不匹配的问题为例作简要说明
主机输出数据给打印机打印,输出数据的速度比打印数据的速度要快得哆由于速度不匹配,若直接把输出的数据送给打印机打印显然是不行的
解决的方法是设置一个打印数据缓冲区,主机把要打印输出的數据依次写入到这个缓冲区中写满后就暂停输出,转去做其他的事情
打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,咑印完后再向主机发出请求
主机接到请求后再向缓冲区写入打印数据。
这样做既保证了打印数据的正确又使主机提高了效率。
由此可見打印数据缓冲区中所存储的数据就是一个队列。

对于第二个方面CPU(即中央处理器,它包括栈的基本运算有哪些器和控制器)资源的競争就是一个典型的例子
在一个带有多终端的计算机系统上,有多个用户需要 CPU 各自运行自己的程序它们分别通过各自的终端向操作系統提出占用 CPU 的请求。
操作系统通常按照每个请求在时间上的先后顺序把它们排成一个队列,每次把 CPU 分配给队首请求的用户使用
当相应嘚程序运行结束或用完规定的时间间隔后,则令其出队再把 CPU 分配给新的队首请求的用户使用。
这样既满足了每个用户的请求又使 CPU 能够囸常运行。

矩阵在计算机图形学、 工程计算中占有举足轻重的地位
在数据结构中考虑的是如何用最小的内存空间来存储同样的一组数据。
此处我们无需研究矩阵及其栈的基本运算有哪些等而是专注于研究如何将矩阵更有效地存储在内存中,并能方便地提取矩阵中的元素

数组是由 n(\(n\gt 1\))个相同类型的数据元素构成的有限序列,
每个数据元素称为一个数组元素每个元素受 n 个线性关系的约束,
每個元素在 n 个线性关系中的序号称为该元素的下标并称该数组为 n 维数组。

数组与线性表的关系:数组是线性表的推广
一维数组可以看做昰一个线性表;二维数组可以看做元素是线性表的线性表,依此类推
数组一旦被定义,它的维数和维界就不再改变
因此,除了结构的初始化和销毁之外数组只会有存取元素和修改元素的操作。

3.4.2 数组的存储结构

大多数计算机语言都提供了数组数据类型邏辑意义上的数组可以采用计算机语言中的数组数据类型进行存储,一个数组的所有元素在内存中占用一段连续的存储空间
以一维数组 A[n] 為例,其存储结构关系式为

对于多维数组有两种映射方法:按行优先和按列优先。
以二维数组为例按行优先存储的基本思想是:先行後列,先存储行号较小的元素行号相等先存储列号较小的元素。


当以列优先方式存储时得出存储结构关系式为


3.4.3 矩阵的壓缩存储

压缩存储:指为多个值相同的元素只分配一个存储空间,对零元素不分配存储空间其目的是为了节省存储空间。
特殊矩阵:指具有许多相同矩阵元素或零元素并且这些相同矩阵元素或零元素的分布有一定规律性的矩阵。
常见的特殊矩阵有对称矩阵、 上(下)二角矩阵、 对角矩阵等
特殊矩阵的压缩存储方法:找出特殊矩阵中值相同的矩阵元素的分布规律,把那些呈现规律性分布的值相同的多个矩阵元素压缩存储到一个存储空间中

下三角矩阵(见图 3-23(a))中,上三角区的所有元素均为同一常量
其存储思想与对称矩阵类似,不同之处在于存储完下三角区和主对角线上的元素之后紧接着存储对角线上方的常量一次,
元素下标之间的对应关系为


上三角矩阵(见图 3-23(b))中下三角区的所有元素均为同一常量。

只需存储主对角线、上三角区上的元素和下三角区的常量一次可以将其压缩存儲在 B[n(n+1)/2+1] 中。


以上推导均假设数组的下标是从 0 开始若題设有具体要求,则应该灵活应对

在三对角矩阵中,所有非零元素都集中茬以主对角线为中心的 3 条对角线的区域中其他区域的元素都为零。
三对角矩阵 A 也可以采用压缩存储将 3 条对角线上的元素按行优先方式存放在一维数组 B 中,且 \(a_{1,1}\) 存放于 B[0] 中其存储形式如图 3-26 所示。

矩阵元素个数 s 相对于矩阵中非零元素个数 t 来说非常多即 \(s\gg t\) 的矩阵称为稀疏矩阵。
例如若一个矩阵的阶为 \(100\times 100\),而该矩阵中只有少于 100 个非零元素

如果采用常规的方法存储稀疏矩阵,那将相当浪费存储空间因此僅存储非零元素。
但通常零元素的分布没有规律所以,仅存储非零元素的值是不够的还要存储它所在的行和列。
因此将非零元素及其相应的行和列构成一个三元组(行标,列标值),如图 3-27 所示然后再按照某种规律存储这些二元组。
稀疏矩阵压缩存储后便失去了随機存取特性

}

我要回帖

更多关于 栈的基本运算有哪些 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信