四、堆栈和队列

堆栈的概念及操作

堆栈的定义

堆栈简称为栈,它是一种只允许在表的一端进行插入和删除操作的线性表。允许操作的 一堆称为栈顶,栈顶元素的位置由一个称为栈顶指针的变量指出;另一端称为栈底。当表中没有元素时,称之为空栈
堆栈的插入操作简称为入栈或者进栈,删除操作称为出栈或者退栈
堆栈操作时按照“后进先出”的原则进行的,因此堆栈又叫后进先出表或者下推表

堆栈的基本操作

堆栈的操作十分简单,通常有几种:

  1. 初始化一个堆栈
  2. 测试堆栈是否为空栈。当堆栈为空时返回一个真值,否则返回一个假值。
  3. 测试堆栈是否已满。当堆栈已满时返回一个真值,否则返回一个假值。
  4. 在堆栈的顶端位置插入一个新的数据元素,简称为进栈或者入栈。
  5. 删除堆栈的栈顶元素,简称为出栈或者退栈。在实际中,通常可以定义一个函数,函数返回栈顶元素,并从堆栈中删除它。
  6. 取当前栈顶元素。该操作与5不同,它不会改变栈顶指针的位置。

堆栈的顺序存储结构

采用顺序存储结构的堆栈简称为顺序堆栈

顺序堆栈的构造

在实际程序设计中,堆栈的顺序存储结构可以利用一个具有M个元素的数组STACK[0,M-1]来描述。其中,STACK作为堆栈的名字,数组的上界M表示堆栈的最大容量。根据堆栈的概念,定义一个整形变量top作为堆栈的栈顶指针,它指出某一时刻堆栈栈顶元素的位置。当堆栈不空时,top的值就是数组某一元素的下标值,这样,STACK[0]为第1个进入堆栈的元素,STACK[i]为第i+1个进入堆栈的元素,STACK[top]为当前的栈顶元素。因此,当堆栈为空栈时,有top=-1 。
由于堆栈是一个动态结构,而数组是一个静态结构,故利用一个静态结构的数组描述一个动态结构的堆栈,存在着所谓的溢出问题。当堆栈中已经有M个元素时,如果此时再做进栈操作则会产生溢出,称这种现象为上溢;对空栈进行删除操作也会产生溢出,称为下溢。为了避免溢出,在对堆栈操作前,应该分别进行测试堆栈已满或者是否为空。

顺序堆栈的基本操作

  1. 初始化一个堆栈
  2. 测试堆栈是否为空
  3. 测试堆栈是否已满
  4. 取当前栈顶元素
  5. 插入(进栈)
  6. 删除(出栈)

多个堆栈共享连续空间

堆栈在实际使用中,经常会出现在一个程序中需要同时使用多个堆栈的情形。为了避免溢出,需要为每一个堆栈分配一个足够大小的空间。然而,要做到这一点往往并不容易,因为:
1、各个堆栈在实际使用过程中所需要的空间大小很难估计;
2、堆栈是个动态结构,各个堆栈的实际大小在使用过程中都会发生变化,有时其中一个堆栈发生了上溢,而其他堆栈可能还留有很多可用空间却不能使用。
这就要求设法来解决多占共享空间的问题。
设将多个堆栈顺序地映射到一个已知大小的存储空间STACK[0,M-1]。

  1. 如果只有两个堆栈来共享这M个存储空间。只需要让第1个堆栈的栈底位于STACK[0]处,而让另一个堆栈的栈底位于STACK[M-1]处。使用堆栈时,两个堆栈各自向它们中间的方向伸展,仅当两个堆栈的栈顶指针相遇时才发生上溢。这样,两个堆栈之间就做到了存储空间余缺互补,相互调剂,从而达到减少空间浪费的目的。
    1、 将item插入第i个堆栈(i=1,2)的算法。(待完善)
    2、删除第i个堆栈栈顶元素的算法。(待完善)
  2. 若有两个以上的堆栈共享连续空间,问题的处理要复杂一些。(待完善)

堆栈的链式存储结构

通常称链式存储方式的堆栈为链接堆栈,甚至就简称为链栈

链接堆栈的构造

链接堆栈是采用一个线性链表实现一个堆栈结构。栈中每一个元素用一个链结点表示,同时,设置一个指针变量(top)指出当前栈顶元素所在链结点的存储位置。当栈顶为空时,有top为null。
优点:1、由于对链接堆栈的操作都是在链表的表头位置进行的,因而相应算法的时间复杂度都为O(1)。2、不必事先声明一片存储区域为堆栈的存储空间,因而不存在因栈满而产生的溢出问题。
所以,若不知道或者难以估计将要进栈元素的最大数量时,采用链式存储结构比采用顺序存储结构更合适。

链接堆栈的基本算法

  1. 链接堆栈初始化
  2. 测试链接堆栈是否为空
  3. 取当前栈顶元素
  4. 链接堆栈的插入
  5. 链接堆栈的删除

堆栈的应用举例

在编译和运行程序的过程中,就需要利用堆栈进行语法检查和表达式求值,在递归过程的实现与函数之间的调用等也都离不开堆栈结构。下面列举几个简单的例子来说明堆栈的具体应用。

  1. 符号匹配检查
    这里只给出算法的核心思想描述:首先,创建一个空的堆栈,依次读入字符直到文件的末尾。如果读得的字符为左花括号或者左圆括号,则将其压入堆栈。如果读得的字符是右花括号或者右圆括号,而此时堆栈为空,则出现不匹配现象,报告错误;否则,退出当前栈顶元素。如果退出的栈顶符号不是对应的做花括号或者左圆括号,则出现不匹配,报告错误。读到文件末尾,若堆栈非空,则报告错误。
  2. 数制转化
    主要使用到依次退栈打印各个元素。
  3. 堆栈在递归中的应用
  4. 表达式的计算
  5. 迷宫

队列的概念及其操作

队列的定义

队列,是一种允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表。允许进行插入(进队)的一端称为队尾,由(rear)指出;允许删除(出队)的一端称为队头,由front指出。没有元素队列称为空队
由于按照“先进先出”的原则进行的,因此也称为 先进先出表。

队列的基本操作

  1. 初始化一个队列
  2. 在队列的尾部插入一个新的元素,称之为进队或者入队。该操作将改变队尾指针的位置。
  3. 删除队头的队头元素。该操作将改变队头指针的位置。
  4. 测试队列是否为空。当队列为空时,返回一个真值;否则,返回一个假值。这是一个队列删除操作之前必须要进行的一个操作。
  5. 测试队列是否已满。当队列已满时,返回一个真值;否则,返回一个假值。这个操作通常只是在队列采用顺序存储结构时,对队列做插入操作之前要进行的一个操作。
  6. 取当前队头元素。该操作与第3个操作不同,后者要修改队头元素指针的位置,而本操作不修改队头元素指针的位置。

队列的顺序存储结构

顺序队列的构造

采用顺序存储结构的队列简称为 顺序队列
顺序队列存在溢出问题。即当队列已满时做进队操作,这种现象称为上溢;而当队列为空时做删除操作,这种现象称为下溢

顺序队列的基本算法

  1. 初始化一个队列
  2. 测试队列是否为空
  3. 取当前队头元素
  4. 队列的插入(进队)
  5. 队列的删除(出队)

循环队列

在队列的插入算法ADDQ中,当Queen[0]~Queen[M-1]均被队列元素占用时,有rear=M-1,导致若此时进行插入操作就会报告产生溢出的信息。细心的同学可能发现,由于每次总是删除当前的队头元素,而插入操作又总是在队尾进行,队列的动态变化又如使队列向右整体移动。当队尾指针rear=M-1时,队列的前端可能还有许多由于此前进行的删除操作而产生的空的位置,因此把这种溢出称为假溢出
解决假溢出问题可能做法之一是:每次删除对头的第1个元素后,就把整个队列往前移动一个位置。算法如delQ1实现。此时在该算法中,对头指针似乎都用不着了,因为按照这种方法,队头元素的位置总是在队列的最前端。很显然,这个算法很不经济,效率极低。若队列中已有1000个元素,某一次删除操作为了删除一个队头元素,竟要移动其他999个元素,可谓牵一发而动全身。为了节省存储空间而白白浪费大量的时间,这种处理方法似乎不足取。
一个聪明的想法是,在初始化队列时令front=rear=0,并且把队列设想成头尾相连的循环表,使得空间得以重复使用,问题迎刃而解。这种队列称为循环队列。在进行插入操作时,当队列的第M个位置(数组下表为M-1)被占用以后,只要队列前面还有可用空间,新的元素加入队列时就可以从第1个位置(数组下表为0)开始。按照这个思路,插入算法中修改队尾指针的语句就可以写成

  1. 循环队列的插入 见addCQ
  2. 循环队列的删除 见delCQ

有趣的是,上述两个不同的算法中,测试循环队列"满"和测试循环队列"空"的条件居然都是front==rear,这样会产生混乱吗?仔细分析就发现,只要当rear加1以后从"后面"赶上来并等于front时才是循环队列”满“的情况,其余情况下的front等于rear均表示循环队列”空“,应该说明的还要一点,此时循环队列的“满”,实际上还要一个空位置(仅一个),但借此能区别“上溢”与“下溢”。当然循环列表不一定是解决假溢出的唯一方法。

队列的链式存储结构

链接队列的构造

所谓队列的链式存储结构是用一个线性链表来表示一个队列,队列中每一个元素对应链表中一个链结点,这样的队列简称为链接队列

链接队列的基本操作

  1. 初始化链接队列
  2. 测试链接队列是否为空
  3. 取当前队头元素
  4. 链接队列的插入
  5. 链接队列的删除
  6. 链接队列的销毁

你可能感兴趣的:(四、堆栈和队列)