栈和队列的概念、表示和实现


前言

  T_T此专栏用于记录数据结构及算法的(痛苦)学习历程,便于日后复习(这种事情不要啊)。所用教材为《数据结构 C语言版 第2版》严蔚敏。


一、栈

1.定义和特点

  栈 (stack) 是限定仅在表尾进行插入或删除操作的线性表。 因此,对栈来说,表尾端有其特殊含义,称为栈顶 (top);相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。
  假设栈 S = (a1, a2, …,an),则称 a1 为栈底元素,an 为栈顶元素。栈中元素按 a1, a2, ···, an的次序进栈, 退栈的第一个元素应为栈顶元素 an。 换句话说,栈的修改是按后进先出的原则进行的。因此,栈又称为后进先出 (Last In First Out, LIFO) 的线性表, 它的这个特点可用铁路调度站形象地表示。
在这里插入图片描述

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

3.顺序栈的表示

  栈有两种存储表示方法,分别称为顺序栈和链栈,顺序栈更为常见。
  顺序栈是指利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针 top 指示栈顶元素在顺序栈中的位置。通常习惯的做法是:以 top=0 表示空栈,鉴于 C 语言中数组的下标约定从 0 开始,则当以 C 语言作描述语言时,如此设定会带来很大不便,因此另设指针 base 指示栈底元素在顺序栈中的位置。当 top 和 base 的值相等时,表示空栈。
  实际上,top 指向栈下一个元素的存放位置,如栈中已有一个元素存放在位置0,则 top 指向位置1;若最后一个元素位置在n,则 top 指向 n+1。
  顺序栈的定义如下:

//----- 顺序栈的存储结构- ----
#define MAXSIZE 100  //顺序栈存储空间的初始分配量
typedef struct {
SElernType *base;  //栈底指针
SElernType *top;   //栈顶指针
int stacksize;   //栈可用最大容量
}SqStack;

在这里插入图片描述

4. 顺序栈基本操作的实现

  由于顺序栈的插入和删除只在栈顶进行, 因此顺序栈的基本操作比顺序表要简单得多。顺序表中给出了顺序表基本操作的实现,可据此类推顺序栈基本操作的实现(真不是偷懒555…)。

二、队列

1.定义和特点

  队列(queue)是一种先进先出(First In First Out, FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。这和日常生活中的排队是一致的,最早进入队列的元素最早离开。在队列中,允许插入的一端称为队尾(rear), 允许删除的一端则称为队头(front)。假设队列为q =(a1, a2, …,an), 那么,a1就是队头元素,an则是队尾元素。队列中的元素是按照 a1,a2,…,an的顺序进入的,退出队列也只能按照这个次序依次退出,也就是说只有在a1, a2…an-1 都退出队列后,an才能退出队列。
在这里插入图片描述

2.队列的抽象数据类型定义

  队列的操作与栈的操作类似,不同的是,队列删除是在表的头部(即队头)进行。下面给出队列的抽象数据类型定义:

ADT Queue (
数据对象:D={ai |ai ∈ ElemSet ,i=l,2,…,n ,n>=0}
数据关系:R={ <ai-1,ai> | ai-1,ai ∈ D , i=2, …,n}
约定其中a1端为队列头,an端为队列尾。
基本操作:
InitQueue(&Q)
操作结果:构造一个空队列Q。
DestroyQueue(&Q)
初始条件:队列Q已存在。
操作结果:队列Q被销毁,不再存在。
ClearQueue(&Q)
初始条件:队列Q巳存在。
操作结果:将Q清为空队列。
QueueEmpty(Q)
初始条件:队列Q已存在。
操作结果:若Q为空队列,则返回true, 否则返回false。
QueueLength(Q)
初始条件:队列Q已存在。
操作结果:返回Q的元素个数,即队列的长度。
GetHead(Q)
初始条件:Q为非空队列。
操作结果:返回Q的队头元素。
EnQueue(&Q, e)
初始条件:队列Q已存在。
操作结果:插入元素e为Q的新的队尾元素。
DeQueue(&Q, &e)
初始条件:Q为非空队列。
操作结果:删除Q的队头元素,并用e 返回其值。
QueueTraverse(Q)
初始条件:Q巳存在且非空。
操作结果:从队头到队尾,依次对Q的每个数据元素访问。
) ADT Queue

3.队列的顺序表示

  队列具有两种存储表示,顺序表示和链式表示。
  和顺序栈相类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需附设两个整型变量 front 和 rear分别指示队列头元素及队列尾元素的位置(后面分别称为头指针和尾指针)。
  为了在 C 语言中描述方便起见,约定:初始化创建空队列时,令 front = rear = 0 , 每当插入新的队列尾元素时,尾指针 rear增1; 每当删除队列头元素时, 头指针 front增1。因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置。
在这里插入图片描述

  队列的顺序存储结构表示如下:

//----- 队列的顺序存储结构-----
#define MAXQSIZE 100  //队列可能达到的最大长度
typedef struct 
{ 
QElemType *base;   //存储空间的基地址
int front;         //头指针
int rear; 		   //尾指针
) SqQueue;

4.循环顺序队列

  假设当前队列分配的最大空间为 6, 则当队列处于下图所示的状态时不可再继续插入新的队尾元素,否则会出现溢出现象, 即因数组越界而导致程序的非法操作错误。 事实上,此时队列的实际可用空间并未占满,所以这种现象称为“假溢出"。这是由 "队尾入队,队头出队” 这种受限制的操作造成的。
在这里插入图片描述
  循环队列可以很好地解决这个问题。
  将顺序队列变为一个环状的空间,头、 尾指针以及队列元素之间的关系不变 ,只是在循环队列中,头、 尾指针"依环状增1"的操作可用"模"运算来实现。 通过取模,头指针和尾指针就可以在顺序表空间内以头尾衔接的方式"循环"移动。
在这里插入图片描述
  在下图(a)中,队头元素是 J5, 在元素J6入队之前,在 Q.rear 的值为 5,当元素J6入队之后,通过"模"运算,Q.rear = (Q.rear + 1)%6, 得到 Q.rear 的值为 0, 而不会出现"假溢出"状态。
  在下图(b)中,J7、J8、J9、J10相继入队,则队列空间均被占满,此时头、尾指针相同。
  在下图(c)中, 若 J5 和 J6 相继从 (a) 所示的队列中出队, 使队列此时呈 "空"的状态,头、尾指针的值也是相同的。

在这里插入图片描述
  由此,循环队列不能以头、尾指针的值是否相同来判别队列空间是"满"还是"空"。
  为了判断循环队列的状态,有两种处理方法:
  1.少用一个元素空间, 即队列空间大小为m时,有m-1个元素就认为是队满。
  2.另设一个标志位以区别队列是"空"还是"满"。

5. 循环顺序队列基本操作的实现

  由于循环顺序队列的插入和删除分别在队列的两端进行, 因此循环顺序队列的基本操作比顺序表要简单得多。顺序表中给出了顺序表基本操作的实现,可据此类推循环顺序队列基本操作的实现(真不是偷懒555…)。


总结

  路漫漫其修远兮,吾将上下而摆烂。
  有任何疑问和补充,欢迎交流。(但我显然不会)

  • 31
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值