首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【数据结构】详谈队列的顺序存储及C语言实现

【数据结构】详谈队列的顺序存储及C语言实现

作者头像
蒙奇D索隆
发布于 2024-01-21 02:50:13
发布于 2024-01-21 02:50:13
1.7K0
举报

循环队列及其基本操作的C语言实现

前言

大家好,很高兴又和大家见面啦!!! 在上一篇内容中,我们在介绍完队列的基本概念、重要术语以及基本操作后,又回顾了一下数据结构的三要素——数据的逻辑结构、数据的存储结构以及数据的运算。 队列这种数据结构我们已经介绍了它的逻辑结构以及数据运算的定义,从这一篇开始,我们将详细介绍队列的数据的存储结构以及数据的运算的实现。 在今天的内容中,我们要介绍的是队列在内存中的顺序存储结构以及如何通过C语言来实现相关的基本操作。

一、队列的顺序存储

顺序存储想必大家都并不陌生了,顺序存储指的是逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系有存储单元的邻接关系来体现。简单的理解就是在内存中分配一块连续的存储单元来存放队列中的元素。

1.1 队尾指针与队头指针

由队列的操作特性可知,我们在对队列进行入队时需要有一个指向队尾的标志,在进行出队时需要有一个指向队头的标志,这样我们才能正常实现数据元素的入队和出队操作。

在栈中,我们将指向栈顶的标志称为栈顶指针,在队列中同理:

  • 指向队尾的标志称为队尾指针(rear)
  • 指向队头的标志称为队头指针(front)

这两个指针的主要做用就是告诉我们元素从哪里入队,从哪里出队。现在了解完了这两个指针,下面我们就来探讨一下如何在队列的顺序存储上来实现队列的基本操作;

1.2 基本操作实现的底层逻辑

为了更好的介绍这些基本操作,我们还是以创建、销毁、增删改查的顺序来进行介绍;

1.2.1 队列的创建与销毁
  1. 队列的创建

对于队列的创建实际上就是定义数据类型、定义队列变量以及初始化一个队列。那如果要定义一个数据类型,按照前面的介绍,队列的数据类型中至少有三个内容:

  1. 存放元素的一块连续的存储空间;
  2. 指向队尾的队尾指针rear
  3. 指向队头的队头指针front

对于这三个内容的实现起始并不复杂,我们可以通过静态数组来实现一块连续的存储空间; 既然是静态数组,那么我们要想找到数组中不同位置的元素那就需要数组下标,因此队头指针与队尾指针就需要是两个存放数组下标的整型变量,因此我们可以将其用C语言表述为:

代码语言:javascript
AI代码解释
复制
//队列的顺序存储类型
#define MaxSize 10 //定义队列的最大长度
typedef int ElemType;//重命名队列中数据元素的数据类型,可以修改为其它数据类型
typedef struct SqQueue {
	ElemType data[MaxSize];//存放队列数据元素的静态数组
	int front, rear;	   //定义队列的队头指针与队尾指针
}SqQueue;				   //重命名后的队列数据类型

那这样是不是就没问题了呢?这里我们先放一放,后面再来讨论;

在定义好数据类型后,我们只需要通过类型来定义一个变量并即将该变量进行初始化,即可完成队列的创建。定义变量都很简单,关键是这个初识我们应该如何表示?

为了更好的理解这个问题,那我们来看一下图片:

按照两个指针的描述,我们在创建队列时应该是如图所示,但是现在就有一个问题,队尾是负责进行入队操作的,队首是负责进行删除操作的,我们应该如何来表示队列的插入与删除呢?

这里我们需要联想一下栈的插入与删除。在栈中如果栈为空栈时,栈顶指针指向的是栈底,入栈一个新元素,栈顶指针就需要往上移动一位来表示入栈;当我们的栈不为空栈时,栈顶指针每往下移动一位就是表示出栈。

也就是说在栈中,我们是借助指针的移动来表示栈顶的出栈与入栈,在队列中,我同样也可以仿照这种思路了,通过指针的移动来表示入队与出队。因此我们在创建一个队列时,可以将frontrear都指向队头,如下所示:

当有新元素入队时,我们可以将队尾指针往上移动,当有元素出栈时,我们同样可以将队头指针往上移动,入下图所示:

既然这样,那我们就可以在初始化时将队头指针与队尾指针都初始化为0,如下所示:

代码语言:javascript
AI代码解释
复制
Q->front = Q->rear = 0;//赋值语句的连续赋值形式
	//等价于下面两句代码
	//Q->rear = 0;
	//Q->front = Q->rear;

具体是不是如此,我们还是先继续往下看。

  1. 队列的销毁

如果我们想要销毁一个队列,无非就是将队列中的所有元素依次出队,那如果一个存满元素的队列要销毁的话,那我们就需要队头指针一直上移如下图所示:

如图所示,当我们在完全销毁队列后,此时的队列的两个指针又重合了,并且都指向了队尾,转化成C语言,则可以表述为:

代码语言:javascript
AI代码解释
复制
if (Q->front == Q->rear && Q->rear == MaxSize - 1)
		printf("队列已销毁\n");

如果只是这样判断,还有没有什么问题呢?我们先继续往后看;


1.2.2 队列的增加与删除

队列的增加与删除,在创建与销毁的讨论中我们已经提到过了,当增加一个新的数据元素时,我们可以通过队尾指针的移动来表示,那现在的问题是,队尾指针是应该如何进行移动?下面就有几种情况:

  1. 先入队新的元素再移动,指针指向队尾元素的下一个位置;
  2. 先移动指针再入队新元素,指针指向队尾元素;

用C语言来表达则是:

代码语言:javascript
AI代码解释
复制
//先入队再移动
	Q->data[Q->rear++] = x;
	//先移动再入队
	Q->data[++Q->rear] = x;

PS:在介绍栈的入栈与出栈时已经介绍过这里的代码简写的意思,这里我就不再重复介绍了。

队列的入队逻辑具体选择哪一种我们先不着急,接着往下看;

当我们要删除一个数据元素时,我们可以通过队头指针的移动来表示,同样的,队头指针的移动和队尾指针一样,也有下面几种情况:

  1. 先出队元素再移动,指针指向的是队头元素;
  2. 先移动指针再出队,指针指向的是队头元素的前一个位置;

用C语言表示则是:

代码语言:javascript
AI代码解释
复制
//先出队再移动
	x = Q->data[Q->front++];
	//先移动再出队
	x = Q->data[++Q->front];

队列的出队逻辑具体选择哪一种我们也不着急,接着往下看;


1.2.3 队列的判空与判满

队列的判空与判满的实现取决于队列初始化的方式,当我们创建好一个队列时,此时的队列中是不存在任何元素的,因此刚创建好的队列是一个空队列,这相信大家应该都能理解。那么我们在判空时,只要按照初始化的方式即可进行判空操作也就是:

代码语言:javascript
AI代码解释
复制
if (Q.front == Q.rear && Q.front == 0)
		printf("队列为空\n");

那判满的话我们则可以根据队头指针与队尾指针来同时判断,如下所示:

代码语言:javascript
AI代码解释
复制
if (Q.front == 0 && Q.rear == MaxSize - 1)
		return true;

但是具体能不能像这样实现呢?下面我们就来仔细探讨一下;


1.2.4 逻辑的局限性

在前面对基本操作的实现中,我们有提到过以下几个问题:

  1. 数据类型只定义静态数组和两个指针行不行?
  2. 队列的初始化能不能将两个指针都初始化为0?
  3. 队列的销毁能不能通过两个指针都指向MaxSize-1来判断是否成功销毁?
  4. 队列的增加与删除的逻辑应该是什么?
  5. 队列的判满与判空能不能实现?

我们来看一下下面的图片:

从上图中我们可以看到按照前面的分析,在创建数据类型时只定义静态数组与两个指针并将指针初始化为0的情况下,我们要实现一个队列,那我们的入队操作与出队操作都应该选择先执行入队或者出队,后执行指针的移动,并且判满与销毁的判定应该是rear==MaxSize;但是这样就会造成一个问题,如下图所示:

在这种情况下,我们此时的队列并不是满队列,但是rear指针已经指向了MaxSize,如果我们要继续入队的话此时就会出现数组越界的情况。

也就是说我们前面的分析只适合与创建好队列后从初始化开始到销毁结束,中间的流程都不能发生任何变化,即入队就要全部元素入满,出队就要直接到销毁。

这样实现的队列就好比一次性碗筷,只能够使用一次,感受一下,并不能重复利用,那我们应该如何调整才能实现一个完整的队列呢?这里就需要引入一个新的概念——循环队列;

二、循环队列

2.1 循环队列的实现逻辑一

所谓的循环队列并不是指像循环链表那种头尾相连的结构,队列的存储结构依然是顺序存储,只不过指针存储的范围是0~MaxSize-1;通过指针的这种存储的循环方式将队列看做一个环,如下图所示:

此时我们各个操作的执行逻辑如下所示:

  1. 数据类型的定义:此时我们只需要定义三个对象——存储数据元素的一维数组,指向队头元素的队头指针与指向队尾元素下一个位置的队尾指针;
  2. 初始化:在初始化时我们将队头指针与队尾指针都初始化为0,使他们同时指向数组首元素的位置;
  3. 判空:我们在进行判空时只需要判断front == rear 这个条件是否成立,成立则为空队列,否则为非空队列;
  4. 入队:入队时的逻辑是先入队后移动,即data[rear] = x;rear++;简写为data[rear++] = x
  5. 判满:为了保证判空的逻辑正常,此时我们需要舍弃最后一个空间来作为判满的条件,也就是说当队尾指针的下一个位置为队头指针时表示队列已满,也就是rear++ == front;当条件成立时表示队列已满,否则表示队列未满;
  6. 出队:在进行出队操作时的逻辑则是先出队后移动,即x = data[front];front++;简写为x = data[front++]
  7. 销毁:在进行销毁时我们需要重复进行出队操作,当队头指针与队尾指针再一次指向同一个位置时,表示队列中的元素已经全部出队,此时队列为空队列,之后我们在将对应的空间释放掉就行,因为是通过静态数组实现,所以当程序结束后,空间就会被操作系统自动回收;

这时可能就有朋友会说了,你像这个样子不就造成了空间的浪费吗?有没有什么办法可以将这一块空间也给利用起来呢?

2.2 循环队列的实现逻辑二

首先对于空间浪费的问题,我想说的是我们这里只浪费了一个数据类型所需的空间,相比于之前的一次性队列来说,我们在利用上面会节省更多的内存空间;最后我们也可以通过对数据类型的调整来完成队列空间的饱和运用,如下所示:

我们通过设定一个记录当前队列长度的变量len,在这种情况下,我们的基本操作就需要做出如下调整:

  1. 数据类型的定义:增设一个记录当前队列长度的变量len
  2. 初始化:在初始化时需要将当前队列长度初始化为0;
  3. 判空:在判空时,我们可以直接判断len == 0是否成立,成立,则为空队列,否则,为非空队列;
  4. 入队:每次完成一个新元素入队后,我们都需要执行一次len++
  5. 判满:在判满时,我们可以直接判断len == MaxSize是否成立,成立队列已入满,否则队列还未满;
  6. 出队:每完成一个元素的出队后,我们都需要执行一次len--
  7. 销毁:在完成所有的元素出队后,此时的队列长度又会变为0,所以我们只需要指向判空操作即可;

这时可能又有朋友会问,你这里是将队尾指针指向的是队尾元素的下一个位置,那如果我将其指向队尾元素又应该如何操作呢?

2.3 循环队列的实现逻辑三

其实这个实现方式也有很多种,这里我们还是以初始化为0的情况下来实现,如下所示:

我们通过增设一个入队标志tag来表示当前空间元素的入队情况,对应的基本操作就有如下调整:

  1. 数据类型的定义:增设一个标志变量tag,当tag = 0时表示此时的空间没有元素入队,当tag = 1时表示此时的空间有元素入队;
  2. 初始化:在初始化阶段,我们需要将队头指针初始化为0,入队标志初始化为0,队尾指针初始化为MaxSize - 1
  3. 判空:在进行判空时,我们需要通过判断rear++ ==front && tag == 0是否成立,成立,则队列为空队列,否则,队列为非空队列;
  4. 入队:在进行入队操作时,此时的入队逻辑是先移动后入队,即rear++;data[rear] = x;简写为data[++rear] = x,并且我们还需要将入队标志tag修改为1,如下图所示:
  1. 判满:在判断队列是否入满时,我们需要判断rear++ == front && tag == 1是否成立,成立则说明此时的队列已满,否则此时的队列未满;
  2. 出队:在进行出队操作时,我们需要将入队标志tag修改为0,如下图所示:
  1. 销毁:在完成所有元素出队后,队列又会变为空队列,因此,我们只需要进行判空即可;

循环队列的实现逻辑主要是有以上三种方式,当然肯定还有其他的方式,但是操作上基本上都是大同小异,现在我们还有一个最重要的问题还有有提到——如何实现队列的循环?

三、如何实现队列的循环

在前面的逻辑中,我们来判断空队列和满队列时有提过通过判断队尾指针的下一个空间是否为队头指针,我们前面也是通过rear++ == front来说明这种判断的过程,但是在实际实现中,这是有问题的,因为我们是通过静态数组实现的队列,它在内存中还是以顺序存储的形式进行存放的,如下所示:

我们是将这种连续存放的空间臆想为一个环状结构,但并不代表它就是一个环状结构,因此,我们只能够通过指针来完成队列的循环。

那如何实现呢?我们现在先来思考一下这两个指针存放的是什么内容?

没错,就是数组下标,在前面我们也有提到过它们的取值范围是0~MaxSize - 1,只要我能够保证不管如何进行入队和出队操作,它们的取值都是在这个范围内,那就能实现队列的循环操作。

在C语言中我们有介绍过一个只能够在整型中使用的算术操作符——%(取模),这个操作符的作用就是获取两个整数相除后的余数,就比如在整型运算中我们知道

,在C语言中我们通过

来获取两个整数的商,通过

来获取两个整数的余数,如下所示:

在除法

中,d的取值肯定是在[0 , b - 1]之间的,也就是说如果通过对下标进行与MaxSize取模的话,那是不是就能保证指针存储的值一定是在[0 , MaxSize - 1]之间了呢?下面我们可以通过代码来测试一下,如下所示:

可以看到,确实如此,通过取模运算后得到的值正好是在[0 , 8]之间。因此循环队列的实现就需要借助取模运算符来完成。下面我们就来看一下循环队列的C语言实现;

四、循环队列的C语言实现

前面我们介绍了3中实现方式,对于记录队列长度的实现相比之下会简单一点,这里我就不多做介绍了,我们主要是介绍另外两种方式,这里我们将这两种方式分别叫做空间置换法标志法。这里的空间置换指的是舍弃小块空间来获取整个队列的空间复用;标志法指的是通过设立入队标志来完成循环队列

4.1 空间置换法的C语言实现

4.1.1 数据类型的定义

空间置换法在定义类型时,总共定义了三个对象——静态数组、队头指针与队尾指针,它的实现就比较简单,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的顺序存储类型——空间置换法
#define MaxSize 10 //定义队列的最大长度
typedef int ElemType;//重命名队列中数据元素的数据类型,可以修改为其它数据类型
typedef struct SqQueue {
	ElemType data[MaxSize];//存放队列数据元素的静态数组
	int front, rear;	   //定义队列的队头指针与队尾指针
}SqQueue;				   //重命名后的队列数据类型

在定义好数据类型后,我们就可以通过数据类型来定义一个队列Q,如下所示:

代码语言:javascript
AI代码解释
复制
void test_Queue1() {
	SqQueue Q;//定义队列Q
}
4.1.2 队列的初始化

在初始化阶段,我们只需要将两个指针初始化为0就行,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的初始化
bool InitQueue(SqQueue* Q) {
	if (!Q)
		return false;//当指针Q为空指针时,返回false
	Q->front = Q->rear = 0;//赋值语句的连续赋值形式
	return true;
}

一定要注意,当我们需要对实参进行修改时,是需要通过传址的方式进行传参,而形参则需要通过指针来接收。我们如果需要调用对应的函数时,一定要对形参进行判空,如果此时的形参为空指针,那说明我们的传参出现了问题,这里千万要记得;

4.1.3 队列的判空

对于空间置换法的判空,我们是根据两个指针是否相等来判断的,所以此时直接判断就是,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的判空
bool EmptyQueue(SqQueue Q) {
	if (Q.rear == (Q.front))
		return true;
	return false;
}
4.1.4 队列的判满

在进行判满时,因为此时的两个指针在逻辑上应该是处于相邻的位置,也就是队尾指针的下一个空间就是队头指针指向的空间,因此这里我们就需要通过

操作符来完成逻辑上的相邻,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的判满
bool FullQueue(SqQueue Q) {
	if ((Q.rear + 1) % MaxSize == Q.front)
		return true;
	return false;
}
4.1.5 队列的入队

在进行入队操作时,因为我们要确保队尾指针的取值是循环的,因此我们在移动队尾指针时就需要借助取模操作符,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的入队
bool EnQueue(SqQueue* Q, ElemType x) {
	if (!Q)
		return false;
	if (FullQueue(*Q))//判满
		return false;
	Q->data[Q->rear] = x;//先入队
	Q->rear = ++(Q->rear) % MaxSize;//再移动
	return true;
}

我们可以执行入队操作的前提条件是此时的队列还未满,因此,在入队前我们需要调用一下判满函数,来确保此时的队列还未满。

在调用函数的时候一定要注意,此时在入队函数中的参数Q是一个指针,但是判满函数的参数Q是一个变量,这里我们需要先对指针Q进行解引用,再传参

4.1.6 队列的出队

在进行出队操作时,我们同样也是需要借助

操作符来确保队头指针的取值是循环的,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的出队
bool DeQueue(SqQueue* Q, ElemType* x) {
	if (!Q)
		return false;
	if (EmptyQueue(*Q))//判空
		return false;
	*x = Q->data[Q->front];//先出队
	Q->front = ++(Q->front) % MaxSize;//再移动
	return true;
}

执行出队的前提条件是此时的队列为非空队列,因此,在出队前我们需要调用一下判空函数,来确保此时的队列为非空队列;

4.1.7 队列的查找

在队列中,我们的查找也是受到限制的,我们不能越过队头或者队尾来访问其他元素,因此,这里我们在实现查找时,只能够查找队头或者队尾元素,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的查找
bool GetHead(SqQueue Q,ElemType* x) {
	if (EmptyQueue(Q))//判空
		return false;
	*x = Q.data[Q.front];//查找队头元素
	return true;
}

我们在进行队列元素访问时,只能从出队的一端来访问元素,因此,队列的查找只支持访问队头元素。

4.1.8 队列的销毁

队列的销毁就是通过重复进行出队操作使队列变成空队列,最后再将队列的空间回收即可完成销毁,因此我们指向销毁时需要判断队列是否为空,如下所示:

代码语言:javascript
AI代码解释
复制
//队列的销毁
bool DestroyQueue(SqQueue* Q) {
	if (!Q)
		return false;
	while (EmptyQueue(*Q)) {
		int x = 0;
		if (DeQueue(Q, &x))
			printf("元素%d已成功出队\n", x);
		else
			printf("出队失败\n");
	}
	return true;
}
4.1.9 空间置换法的演示

在完成了这些基本操作后,下面我们就来演示一下空间置换法,代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的顺序存储类型——空间置换法
#define MaxSize 10 //定义队列的最大长度
typedef int ElemType;//重命名队列中数据元素的数据类型,可以修改为其它数据类型
typedef struct SqQueue {
	ElemType data[MaxSize];//存放队列数据元素的静态数组
	int front, rear;	   //定义队列的队头指针与队尾指针
}SqQueue;				   //重命名后的队列数据类型
//队列的初始化
bool InitQueue(SqQueue* Q) {
	if (!Q)
		return false;//当指针Q为空指针时,返回false
	Q->front = Q->rear = 0;//赋值语句的连续赋值形式
	return true;
}
//队列的判空
bool EmptyQueue(SqQueue Q) {
	if (Q.rear == (Q.front))
		return true;
	return false;
}
//队列的判满
bool FullQueue(SqQueue Q) {
	if ((Q.rear + 1) % MaxSize == Q.front)
		return true;
	return false;
}
//队列的入队
bool EnQueue(SqQueue* Q, ElemType x) {
	if (!Q)
		return false;
	if (FullQueue(*Q))//判满
		return false;
	Q->data[Q->rear] = x;//先入队
	Q->rear = ++(Q->rear) % MaxSize;//再移动
	return true;
}
//队列的出队
bool DeQueue(SqQueue* Q, ElemType* x) {
	if (!Q)
		return false;
	if (EmptyQueue(*Q))//判空
		return false;
	*x = Q->data[Q->front];//先出队
	Q->front = ++(Q->front) % MaxSize;//再移动
	return true;
}
//队列的查找
bool GetHead(SqQueue Q,ElemType* x) {
	if (EmptyQueue(Q))//判空
		return false;
	*x = Q.data[Q.front];//查找队头元素
	return true;
}

//队列的销毁
bool DestroyQueue(SqQueue* Q) {
	if (!Q)
		return false;
	while (EmptyQueue(*Q)) {
		int x = 0;
		if (DeQueue(Q, &x))
			printf("元素%d已成功出队\n", x);
		else
			printf("出队失败\n");
	}
	return true;
}
void test_Queue1() {
	SqQueue Q;//定义队列Q
	if (InitQueue(&Q))//队列初始化
		printf("初始化成功\n");
	else {
		printf("初始化失败\n");
		return;
	}
	int x = 0;
	if (GetHead(Q,&x))//查找队头元素
		printf("此时的队列为非空队列,队头元素为%d\n", x);
	else {
		printf("此时的队列为空队列\n");
	}
	//元素入队
	while (scanf("%d", &x) == 1) {
		if (EnQueue(&Q, x)) {
			printf("元素%d入队成功\n", x);
		}
		else {
			printf("元素%d入队失败\n", x);
			if (FullQueue(Q))//插入失败后进行判满
				printf("此时的队列已满\n");
			else {
				printf("此时的队列未满\n");
			}
		}
	}
	//元素出队
	if (DeQueue(&Q, &x)) {
		printf("元素%d出队成功\n", x);
		if (GetHead(Q, &x))//查找队头元素
			printf("此时的队列为非空队列,队头元素为%d\n", x);
		else {
			printf("此时的队列为非空队列\n");
		}
	}
	else {
		printf("出队失败\n");
		if (EmptyQueue(Q))//队列判空
			printf("此时的队列为空队列\n");
		else
			printf("此时的队列为非空队列\n");
	}
	//队列销毁
	if (DestroyQueue(&Q)) {
		printf("队列成功销毁\n");
	}
	else {
		printf("队列销毁失败\n");
	}
}

接下来我们在主函数中调用一下test_Queue1函数来测试一下空间置换法:

可以看到,此时所有的功能都能够正常运行,也就是说我们完成了通过空间置换法完成了一个循环队列;

4.2 标志法的C语言实现

4.2.1 数据类型的定义

在标志法中,我们增设了一个出入队的标志,对应的数据类型如下所示:

代码语言:javascript
AI代码解释
复制
//队列的顺序存储类型——标志法
#define MaxSize 10 //定义队列的最大长度
typedef int ElemType;//重命名队列中数据元素的数据类型,可以修改为其它数据类型
typedef struct SqQueue {
	ElemType data[MaxSize];//存放队列数据元素的静态数组
	int front, rear;	   //定义队列的队头指针与队尾指针
	int tag;			   //出入队标志
}SqQueue;				   //重命名后的队列数据类型
4.2.2 队列的初始化

出入队的标志取值,我们将其设定为出队为0,入队为1,

代码语言:javascript
AI代码解释
复制
//队列的初始化
bool InitQueue(SqQueue* Q) {
	if (!Q)
		return false;//当指针Q为空指针时,返回false
	Q->front = 0;//队头指针指向的是队头元素所在空间
	Q->rear = MaxSize - 1;//队尾指针指向的是队尾元素所在空间
	Q->tag = 0;//此时队列为空队列,相当于执行了出队操作
	return true;
}
4.2.3 队列的判空

当队列为空队列时,此时队头指针与队尾指针在逻辑上是相邻的,我们可以通过队尾指针向上移动找到队头指针,并且此时的入队标志为0,表示的是通过出队操作的到的对应关系,代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的判空
bool EmptyQueue(SqQueue Q) {
	if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 0)
		return true;
	return false;
}

同样的,为了保证指针的可循环性,我们这里在判断时是借助了

操作符实现的相邻判断;

4.2.4 队列的判满

在标志法的实现下,判空与判满两个指针的位置关系是一致的,唯一不同的就是入队标志,当标志为1时说明此时是通过入队得到的对应的位置关系,那就说明此时是队列已满的情况,代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的判满
bool FullQueue(SqQueue Q) {
	if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 1)
		return true;
	return false;
}
4.2.5 队列的入队

当队尾指针指向的是队尾元素时,我们在进行入队操作是需要先将队尾指针往后移动一位后,再进行入队操作,由于设立了入队标志,所以当我们执行入队操作时,需要将入队标志改为1,对应的代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的入队
bool EnQueue(SqQueue* Q, ElemType x) {
	if (!Q)
		return false;
	if (FullQueue(*Q))//判满
		return false;
	Q->rear = ++(Q->rear) % MaxSize;//先移动
	Q->data[Q->rear] = x;//再入队
	Q->tag = 1;//执行入队操作,入队标志改为1
	return true;
}
4.2.6 队列的出队

队列的出队操作唯一需要改动的就是将入队标志修改为0,代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的出队
bool DeQueue(SqQueue* Q, ElemType* x) {
	if (!Q)
		return false;
	if (EmptyQueue(*Q))//判空
		return false;
	*x = Q->data[Q->front];//先出队
	Q->front = ++(Q->front) % MaxSize;//再移动
	Q->tag = 0;//指向出队操作,入队标志改为0
	return true;
}
4.2.7 队列的查找

对于两种方法的查找而言,都是一致的,因为我此时只需要找到队头或者队尾元素即可;

代码语言:javascript
AI代码解释
复制
//队列的查找
bool GetHead(SqQueue Q, ElemType* x) {
	if (EmptyQueue(Q))//判空
		return false;
	*x = Q.data[Q.front];//查找队头元素
	return true;
}
4.2.8 队列的销毁

标志法的销毁操作,同样是通过重复调用出队操作,因此,这里也是不需要有任何修改,代码如下所示:

代码语言:javascript
AI代码解释
复制
//队列的销毁
bool DestroyQueue(SqQueue* Q) {
	if (!Q)
		return false;
	while (EmptyQueue(*Q)) {
		int x = 0;
		if (DeQueue(Q, &x))
			printf("元素%d已成功出队\n", x);
		else
			printf("出队失败\n");
	}
	return true;
}
4.2.9 标志法的C语言实现

下面我们就来看一下整体的代码:

代码语言:javascript
AI代码解释
复制
//队列的顺序存储类型——标志法
#define MaxSize 10 //定义队列的最大长度
typedef int ElemType;//重命名队列中数据元素的数据类型,可以修改为其它数据类型
typedef struct SqQueue {
	ElemType data[MaxSize];//存放队列数据元素的静态数组
	int front, rear;	   //定义队列的队头指针与队尾指针
	int tag;			   //出入队标志
}SqQueue;				   //重命名后的队列数据类型
//队列的初始化
bool InitQueue(SqQueue* Q) {
	if (!Q)
		return false;//当指针Q为空指针时,返回false
	Q->front = 0;//队头指针指向的是队头元素所在空间
	Q->rear = MaxSize - 1;//队尾指针指向的是队尾元素所在空间
	Q->tag = 0;//此时队列为空队列,相当于执行了出队操作
	return true;
}
//队列的判空
bool EmptyQueue(SqQueue Q) {
	if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 0)
		return true;
	return false;
}
//队列的判满
bool FullQueue(SqQueue Q) {
	if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 1)
		return true;
	return false;
}
//队列的入队
bool EnQueue(SqQueue* Q, ElemType x) {
	if (!Q)
		return false;
	if (FullQueue(*Q))//判满
		return false;
	Q->rear = ++(Q->rear) % MaxSize;//先移动
	Q->data[Q->rear] = x;//再入队
	Q->tag = 1;//执行入队操作,入队标志改为1
	return true;
}
//队列的出队
bool DeQueue(SqQueue* Q, ElemType* x) {
	if (!Q)
		return false;
	if (EmptyQueue(*Q))//判空
		return false;
	*x = Q->data[Q->front];//先出队
	Q->front = ++(Q->front) % MaxSize;//再移动
	Q->tag = 0;//指向出队操作,入队标志改为0
	return true;
}
//队列的查找
bool GetHead(SqQueue Q, ElemType* x) {
	if (EmptyQueue(Q))//判空
		return false;
	*x = Q.data[Q.front];//查找队头元素
	return true;
}

//队列的销毁
bool DestroyQueue(SqQueue* Q) {
	if (!Q)
		return false;
	while (EmptyQueue(*Q)) {
		int x = 0;
		if (DeQueue(Q, &x))
			printf("元素%d已成功出队\n", x);
		else
			printf("出队失败\n");
	}
	return true;
}
void test_Queue2() {
	SqQueue Q;//定义队列Q
	if (InitQueue(&Q))//队列初始化
		printf("初始化成功\n");
	else {
		printf("初始化失败\n");
		return;
	}
	int x = 0;
	if (GetHead(Q, &x))//查找队头元素
		printf("此时的队列为非空队列,队头元素为%d\n", x);
	else {
		printf("此时的队列为空队列\n");
	}
	//元素入队
	while (scanf("%d", &x) == 1) {
		if (EnQueue(&Q, x)) {
			printf("元素%d入队成功\n", x);
		}
		else {
			printf("元素%d入队失败\n", x);
			if (FullQueue(Q))//插入失败后进行判满
				printf("此时的队列已满\n");
			else {
				printf("此时的队列未满\n");
			}
		}
	}
	//元素出队
	if (DeQueue(&Q, &x)) {
		printf("元素%d出队成功\n", x);
		if (GetHead(Q, &x))//查找队头元素
			printf("此时的队列为非空队列,队头元素为%d\n", x);
		else {
			printf("此时的队列为非空队列\n");
		}
	}
	else {
		printf("出队失败\n");
		if (EmptyQueue(Q))//队列判空
			printf("此时的队列为空队列\n");
		else
			printf("此时的队列为非空队列\n");
	}
	//队列销毁
	if (DestroyQueue(&Q)) {
		printf("队列成功销毁\n");
	}
	else {
		printf("队列销毁失败\n");
	}
}

下面我们就来在主函数中调用并测试一下看看结果:

可以看到此时的标志法实现时能够比空间置换法要多存储一个元素,我们现在也成功使用C语言通过标志法实现了循环队列。

结语

在今天的篇章中,我们详细介绍了队列的顺序存储结构——循环队列,并详细分析了三种实现循环队列的方式,最后通过C语言实现了两种循环队列——空间置换法与标志法,希望今天的内容能够帮助大家在了解队列的顺序存储结构的同时,还能帮助大家熟悉对应的基本操作并能够实现对应的操作。

今天的内容到这里就全部结束了,在下一个篇章中,我们将开始介绍队列的链式存储结构,通过链式存储的队列的基本操作又应该如何实现呢?就让我们期待一下下一篇的内容介绍吧!最后感谢大家的翻阅,咱们下一篇再见!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【数据结构】在链队列中你可能忽视的二三事
大家好,很高兴又和大家见面啦!!! 在上一个篇章中,我们详细的介绍了队列的顺序存储结构——循环队列。同时花费了大量的篇幅来介绍循环队列的实现逻辑与实现方式,最后我们还使用C语言通过两种方式是实现了循环队列,相信大家看完上一篇内容的话应该对循环队列及其基本操作的实现已经没什么问题了。如果对这些内容还有疑问的朋友可以在文章下方评论留言,或者私信博主,博主看到后都会回复的哦!
蒙奇D索隆
2024/01/23
2790
【数据结构】在链队列中你可能忽视的二三事
队列(常用数据结构之一)
那么a1为对头元素,an为队尾元素。最早进入队列的元素也会最早出来,只有当最先进入队列的元素都出来以后,后进入的元素才能退出。 在日常生活中,人们去银行办理业务需要排队,这就类似我们提到的队列。每一个新来办理业务的需要按照机器自动生成的编号等待办理,只有前面的人办理完毕,才能轮到排在后面的人办理业务。新来的人进入排队状态就相当于入队,前面办理完业务离开的就相当于出队。队列有两种存储表示:顺序存储和链式存储。采用顺序存储结构的队列被称为顺序队列,采用链式存储结构的队列称为链式队列。 基本运算 InitQueue() ——初始化队列 EnQueue() ——进队列 DeQueue() ——出队列 IsQueueEmpty() ——判断队列是否为空 IsQueueFull() ——判断队列是否已满 顺序队列 由于顺序队列的底层使用的是数组,因此需预先申请一块足够大的内存空间初始化顺序队列。除此之外,为了满足顺序队列中数据从队尾进,队头出且先进先出的要求,我们还需要定义两个指针(top 和 rear)分别用于指向顺序队列中的队头元素和队尾元素。 队列为空时,队头指针front和队尾指针rear都指向下标为0的存储单元,当元素a,b,c,d,e,f,g依次进入队列后,元素a~g分别存放在数组下标为0~6的存储单元中,队头指针front指向元素a,队尾指针指rear向元素g的下一位置。如图所示。
后端码匠
2020/12/09
7040
队列(常用数据结构之一)
队列(顺序存储结构)
这里我新加了一个打印函数,并且我只写了循环队列,教材有两种,一种是循环队列,一种是顺序队列, 但是顺序队列实在太耗空间了,基本用不到,所以我就直接跳了
废江_小江
2022/09/05
6040
数据结构代码题-栈、队列
04.设单链表的表头指针为L,结点结构由data和next两个域构成,其中data域为字符型。试设计算法判断该链表的全部n个字符是否中心对称。例如xyx、xyyx都是中心对称。
愷龍
2023/10/16
3850
数据结构代码题-栈、队列
重要的数据结构--队列(C语言实现)
头文件(如果把头文件和程序代码都放在一个工程里,则头文件不能用< >(只有系统头文件才能用),在工程里面的头文件,在主程序调用时,用“”)
跋扈洋
2021/02/02
7320
数据结构-队列
本文介绍了循环队列和链队列的区别,以及它们的实现细节。循环队列是一种先进先出(FIFO)的数据结构,而链队列是一种后进先出(LIFO)的数据结构。循环队列通过两个指针(一个头指针,一个尾指针)来管理队列,链队列则通过一个指针进行头尾指针的切换。在性能上,循环队列由于不需要额外的空间,因此比链队列更高效。然而,链队列在不需要考虑队列长度的情况下,可以更灵活地插入和删除元素。
chaibubble
2018/01/02
6440
数据结构-队列
队列(链式存储结构)
设有n个人站成一排,从左向右的编号分别为1-n,现在从左边往右报数“1,2,1,2,。。。“,数到”1“的人出列,数到”2”的人立即站到队伍的最右端。报数过程反复进行,直到n个人都出列为止。要求给出他们的出列顺序。 例如,当n=8时初始序列为: 1 2 3 4 5 6 7 8 则出列顺序为: 1 3 5 7 2 6 4 8
废江_小江
2022/09/05
6210
队列(链式存储结构)
数据结构之循环队列
前言: 关于循环队列需明白以下几点: 1、循环队列是队列的顺序存储结构 2、循环队列用判断是否为空利用 Q.front=Q.rear 3、循环队列头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置 4、按照队列的定义,队头删除,队尾插入,在这里插入图片描述会导致队头之前可能有空余的内存空间(如下图J1,J2出队后,空间被浪费),为了解决该问题,提出循环队列的解决方案
全栈程序员站长
2022/09/05
3450
数据结构之循环队列
队列的基本概念详解,循环队列、链式队列的C++详细实现
队列的顺序存储形式,可以用一段连续的空间存储数据元素,用两个整型变量记录队头和队尾元素的下标。
莫浅子
2022/11/18
1.6K0
队列的基本概念详解,循环队列、链式队列的C++详细实现
数据结构C语言实验三之循环队列
算法设计题:对于循环队列,利用队列的基本运算,设计删除队列中从队头开始的第k个元素的算法。
菜菜有点菜
2023/11/23
3330
数据结构C语言实验三之循环队列
C++数据结构——队列「建议收藏」
(1)队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构;
全栈程序员站长
2022/08/22
3.5K0
C++数据结构——队列「建议收藏」
2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
将循环队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。 基本操作:
盛透侧视攻城狮
2024/10/22
2760
2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构–循环队列[通俗易懂]
初始化时rear = front = 0 ,插入新的元素时尾指针加1,元素出队列时队头指针加1。
全栈程序员站长
2022/09/05
1.2K0
数据结构 第三章栈和队列
假设在周末舞会上,男士和女士们分别进入舞厅,各自排成一队。跳舞开始,依次从男队和女队队头各出一人配成舞伴,若两队初始人数不同,则较长那一队未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。 你需要用队列操作实现上述算法。请完成下面5个函数的操作。
十二惊惶
2024/02/28
6490
数据结构 第7讲 循环队列
过了一段时间,小张再也受不了这种"起早贪黑"的有车生活。为了解决胡同停车问题,小张跑了无数次居委会,终于将挡在胡同口的建筑清除,这样住在胡同尽头的小张,就可以早早回家停在家门口,每天第一个开车上班去了。
rainchxy
2018/09/13
9600
数据结构 第7讲 循环队列
数据结构 第三章 栈和队列
栈顶:通常将表中允许进行插入、删除操作的一端称为栈顶 (Top),因此栈顶的当前位置是动态变化的,它由一个称为栈顶指针的位置指示器指示。
Twcat_tree
2022/11/29
7800
数据结构 第三章 栈和队列
浅谈栈与队列
栈(Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。
一条晒干的咸鱼
2024/11/19
1590
浅谈栈与队列
C语言数据结构与算法--简单实现队列的入队和出队
和栈相反,队列(Queue)是一种先进先出(First In First Out)的线性表。只 允许在表的一端进行插入,而在另一端删除元素,如日常生活中的排队现象。队列中 允许插入的一端叫队尾(rear),允许删除的一端称队头(front)。假设队列为 q=(a1, a2, …, an),则 a1 为队头元素, an 为队尾元素。队列中出队的顺序和进队顺序一 致。队列的基本操作与栈类似,包括:初始化、清空、销毁、求长度、得到对头元 素、插入、删除。
用户11404404
2024/12/13
5700
C语言数据结构与算法--简单实现队列的入队和出队
C语言实现顺序队列
顺序队列和顺序栈相类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需附设两个 “指针” front 和 rear 分别指示队列头元素及队列尾元素的位置。
忆想不到的晖
2020/07/15
1.9K0
C语言实现顺序队列
数据结构:队列的顺序存储结构(循环队列)
本文介绍了循环队列的实现方式和应用场景,通过对比循环队列和传统队列的差异,阐述了循环队列的优势和劣势。同时,给出了一种基于填充计数的循环队列实现方法,并给出了相应的代码示例。
s1mba
2018/01/03
1.5K0
数据结构:队列的顺序存储结构(循环队列)
推荐阅读
相关推荐
【数据结构】在链队列中你可能忽视的二三事
更多 >
交个朋友
加入腾讯云官网粉丝站
双11活动抢先看 更有社群专属礼券掉落
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档