当前位置: 首页 > news >正文

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

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

  • 前言
  • 一、队列的顺序存储
    • 1.1 队尾指针与队头指针
    • 1.2 基本操作实现的底层逻辑
      • 1.2.1 队列的创建与销毁
      • 1.2.2 队列的增加与删除
      • 1.2.3 队列的判空与判满
      • 1.2.4 逻辑的局限性
  • 二、循环队列
    • 2.1 循环队列的实现逻辑一
    • 2.2 循环队列的实现逻辑二
    • 2.3 循环队列的实现逻辑三
  • 三、如何实现队列的循环
  • 四、循环队列的C语言实现
    • 4.1 空间置换法的C语言实现
      • 4.1.1 数据类型的定义
      • 4.1.2 队列的初始化
      • 4.1.3 队列的判空
      • 4.1.4 队列的判满
      • 4.1.5 队列的入队
      • 4.1.6 队列的出队
      • 4.1.7 队列的查找
      • 4.1.8 队列的销毁
      • 4.1.9 空间置换法的演示
    • 4.2 标志法的C语言实现
      • 4.2.1 数据类型的定义
      • 4.2.2 队列的初始化
      • 4.2.3 队列的判空
      • 4.2.4 队列的判满
      • 4.2.5 队列的入队
      • 4.2.6 队列的出队
      • 4.2.7 队列的查找
      • 4.2.8 队列的销毁
      • 4.2.9 标志法的C语言实现
  • 结语

封面

前言

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

一、队列的顺序存储

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

1.1 队尾指针与队头指针

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

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

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

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

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

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

1.2.1 队列的创建与销毁

  1. 队列的创建

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

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

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

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

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

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

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

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

也就是说在栈中,我们是借助指针的移动来表示栈顶的出栈与入栈,在队列中,我同样也可以仿照这种思路了,通过指针的移动来表示入队与出队。因此我们在创建一个队列时,可以将frontrear都指向队头,如下所示:
队列的创建2
当有新元素入队时,我们可以将队尾指针往上移动,当有元素出栈时,我们同样可以将队头指针往上移动,入下图所示:
队列的创建3
既然这样,那我们就可以在初始化时将队头指针与队尾指针都初始化为0,如下所示:

	Q->front = Q->rear = 0;//赋值语句的连续赋值形式//等价于下面两句代码//Q->rear = 0;//Q->front = Q->rear;

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

  1. 队列的销毁

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

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

	if (Q->front == Q->rear && Q->rear == MaxSize - 1)printf("队列已销毁\n");

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


1.2.2 队列的增加与删除

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

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

用C语言来表达则是:

	//先入队再移动Q->data[Q->rear++] = x;//先移动再入队Q->data[++Q->rear] = x;

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

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

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

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

用C语言表示则是:

	//先出队再移动x = Q->data[Q->front++];//先移动再出队x = Q->data[++Q->front];

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


1.2.3 队列的判空与判满

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

	if (Q.front == Q.rear && Q.front == 0)printf("队列为空\n");

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

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 循环队列的实现逻辑二

首先对于空间浪费的问题,我想说的是我们这里只浪费了一个数据类型所需的空间,相比于之前的一次性队列来说,我们在利用上面会节省更多的内存空间;
最后我们也可以通过对数据类型的调整来完成队列空间的饱和运用,如下所示:
循环队列2
我们通过设定一个记录当前队列长度的变量len,在这种情况下,我们的基本操作就需要做出如下调整:

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

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

2.3 循环队列的实现逻辑三

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

我们通过增设一个入队标志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,如下图所示:
    循环队列4
  5. 判满:在判断队列是否入满时,我们需要判断rear++ == front && tag == 1是否成立,成立则说明此时的队列已满,否则此时的队列未满;
  6. 出队:在进行出队操作时,我们需要将入队标志tag修改为0,如下图所示:
    循环队列5
  7. 销毁:在完成所有元素出队后,队列又会变为空队列,因此,我们只需要进行判空即可;

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

三、如何实现队列的循环

在前面的逻辑中,我们来判断空队列和满队列时有提过通过判断队尾指针的下一个空间是否为队头指针,我们前面也是通过rear++ == front来说明这种判断的过程,但是在实际实现中,这是有问题的,因为我们是通过静态数组实现的队列,它在内存中还是以顺序存储的形式进行存放的,如下所示:
指针的循环
我们是将这种连续存放的空间臆想为一个环状结构,但并不代表它就是一个环状结构,因此,我们只能够通过指针来完成队列的循环。

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

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

在C语言中我们有介绍过一个只能够在整型中使用的算术操作符——%(取模),这个操作符的作用就是获取两个整数相除后的余数,就比如在整型运算中我们知道 5 / 2 = 2 … … 1 5/2=2……1 5/2=2……1,在C语言中我们通过 / / /来获取两个整数的商,通过 % \% %来获取两个整数的余数,如下所示:
指针的循环2
在除法 a / b = c … … d a / b = c …… d a/b=c……d中,d的取值肯定是在[0 , b - 1]之间的,也就是说如果通过对下标进行与MaxSize取模的话,那是不是就能保证指针存储的值一定是在[0 , MaxSize - 1]之间了呢?

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

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

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

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

4.1.1 数据类型的定义

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

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

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

void test_Queue1() {SqQueue Q;//定义队列Q
}

4.1.2 队列的初始化

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

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

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

4.1.3 队列的判空

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

//队列的判空
bool EmptyQueue(SqQueue Q) {if (Q.rear == (Q.front))return true;return false;
}

4.1.4 队列的判满

在进行判满时,因为此时的两个指针在逻辑上应该是处于相邻的位置,也就是队尾指针的下一个空间就是队头指针指向的空间,因此这里我们就需要通过 % \% %操作符来完成逻辑上的相邻,如下所示:

//队列的判满
bool FullQueue(SqQueue Q) {if ((Q.rear + 1) % MaxSize == Q.front)return true;return false;
}

4.1.5 队列的入队

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

//队列的入队
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 队列的出队

在进行出队操作时,我们同样也是需要借助 % \% %操作符来确保队头指针的取值是循环的,如下所示:

//队列的出队
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 队列的查找

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

//队列的查找
bool GetHead(SqQueue Q,ElemType* x) {if (EmptyQueue(Q))//判空return false;*x = Q.data[Q.front];//查找队头元素return true;
}

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

4.1.8 队列的销毁

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

//队列的销毁
bool DestroyQueue(SqQueue* Q) {if (!Q)return false;while (EmptyQueue(*Q)) {int x = 0;if (DeQueue(Q, &x))printf("元素%d已成功出队\n", x);elseprintf("出队失败\n");}return true;
}

4.1.9 空间置换法的演示

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

//队列的顺序存储类型——空间置换法
#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为空指针时,返回falseQ->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);elseprintf("出队失败\n");}return true;
}
void test_Queue1() {SqQueue Q;//定义队列Qif (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");elseprintf("此时的队列为非空队列\n");}//队列销毁if (DestroyQueue(&Q)) {printf("队列成功销毁\n");}else {printf("队列销毁失败\n");}
}

接下来我们在主函数中调用一下test_Queue1函数来测试一下空间置换法:
空间置换法的演示
可以看到,此时所有的功能都能够正常运行,也就是说我们完成了通过空间置换法完成了一个循环队列;

4.2 标志法的C语言实现

4.2.1 数据类型的定义

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

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

4.2.2 队列的初始化

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

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

4.2.3 队列的判空

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

//队列的判空
bool EmptyQueue(SqQueue Q) {if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 0)return true;return false;
}

同样的,为了保证指针的可循环性,我们这里在判断时是借助了 % \% %操作符实现的相邻判断;

4.2.4 队列的判满

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

//队列的判满
bool FullQueue(SqQueue Q) {if (((Q.rear + 1) % MaxSize) == Q.front && Q.tag == 1)return true;return false;
}

4.2.5 队列的入队

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

//队列的入队
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;//执行入队操作,入队标志改为1return true;
}

4.2.6 队列的出队

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

//队列的出队
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;//指向出队操作,入队标志改为0return true;
}

4.2.7 队列的查找

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

//队列的查找
bool GetHead(SqQueue Q, ElemType* x) {if (EmptyQueue(Q))//判空return false;*x = Q.data[Q.front];//查找队头元素return true;
}

4.2.8 队列的销毁

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

//队列的销毁
bool DestroyQueue(SqQueue* Q) {if (!Q)return false;while (EmptyQueue(*Q)) {int x = 0;if (DeQueue(Q, &x))printf("元素%d已成功出队\n", x);elseprintf("出队失败\n");}return true;
}

4.2.9 标志法的C语言实现

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

//队列的顺序存储类型——标志法
#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为空指针时,返回falseQ->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;//执行入队操作,入队标志改为1return 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;//指向出队操作,入队标志改为0return 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);elseprintf("出队失败\n");}return true;
}
void test_Queue2() {SqQueue Q;//定义队列Qif (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");elseprintf("此时的队列为非空队列\n");}//队列销毁if (DestroyQueue(&Q)) {printf("队列成功销毁\n");}else {printf("队列销毁失败\n");}
}

下面我们就来在主函数中调用并测试一下看看结果:
标志法的演示
可以看到此时的标志法实现时能够比空间置换法要多存储一个元素,我们现在也成功使用C语言通过标志法实现了循环队列。

结语

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

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

相关文章:

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

循环队列及其基本操作的C语言实现 前言一、队列的顺序存储1.1 队尾指针与队头指针1.2 基本操作实现的底层逻辑1.2.1 队列的创建与销毁1.2.2 队列的增加与删除1.2.3 队列的判空与判满1.2.4 逻辑的局限性 二、循环队列2.1 循环队列的实现逻辑一2.2 循环队列的实现逻辑二2.3 循环队…...

为什么 HTTPS 协议能保障数据传输的安全性?

HTTP 协议 在谈论 HTTPS 协议之前,先来回顾一下 HTTP 协议的概念。 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议,它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯,目前协议由之前的 RFC 2616 拆…...

使用 Node 创建 Web 服务器

Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块,代码如下: var http require(http); 以下是演示一个最基本的 HTTP 服务器架构(使用 8080 端口)&#x…...

leetcode 151反转字符串如何原地去除多余空格

题目:https://leetcode.cn/problems/reverse-words-in-a-string/description/ 完整题解:https://leetcode.cn/problems/reverse-words-in-a-string/solutions/2611893/chu-li-kong-ge-ku-han-shu-reversefan-zhu-bioo 思路来自代码随想录,对其中的除去多…...

面试问题记录【深圳,共三面,A 轮公司】

问题记录 一面: 自我介绍项目介绍项目中用到的本地缓存是否涉及数据不一致问题,如何解决?项目中用到了 RTree 和普通的 B 树和 B树的数据结构的区别是什么?mysql 数据库中几种日志的用法和区别?redis 中缓存三兄弟存…...

Mysql数据库cpu飙升怎么解决

排查过程 (1)使用top命令观察,确定是mysql导致还是其他原因。 (2)如果是mysql导致的,show processlist,查看session情况,确定是不是有消耗资源的sql在运行。 (3&#xf…...

PHP反序列化漏洞-POP链构造

POP链构造 POP链(Property-Oriented Programming)是一种常用于构造特定调用链的方法,用于从现有运行环境中寻找一系列代码或指令调用。它的目的是构成一组连续的调用链,最终达到攻击者恶意利用的目的。POP链实质上是通过控制对象的可控属性来控制程序的执行流程,从而利用…...

CentOS 7安装Java并配置环境

一、安装Java环境 1、检查系统是否安装Java [rootlocalhost ~]# java -version 2、更新系统软件包 [rootlocalhost ~]# yum update #遇到[y/n],选择y并回车,耐心等待下载完毕,之后系统会自动检验更新的软件包遇到 /var/run/yum.pid 已被锁定 /var/…...

Vagrant创建Oracle RAC环境示例

利用Vagrant安装Oracle RAC(默认为non-CDB模式),生成2台虚机,耗时约1小时。 node1: -----------------------------------------------------------------node1: INFO: 2024-01-11 18:25:54: Make create database commandnode1: …...

鸿蒙 HarmonyOS ArkTS ArkUI 动画 中心缩放、顶部缩放、纵向缩放

EntryComponentstruct Index {State widthA: number 200State heightA: number 200onPageShow():void{animateTo ( {duration: 2000,iterations: -1,curve:Curve.Linear}, () > {this.widthA 0this.heightA 0} )}build() {Column() {// 中心缩放Column(){}.width(this.wi…...

基于python socket实现TCP/UDP通信

两个应用程序如果需要进行通讯最基本的一个前提就是能够唯一的标示一个进程,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中…...

指针的运算

指针的运算 1.指针-整数 #define N_VALUES 5 float values[N_VALUES]; float* vp; //指针-整数:指针的关系运算 int main() { for (vp &values[0]; vp < &values[N_VALUES];) { *vp 0;//指针每自增一次,就是指向下一个元素的地址 } return …...

记录一次QT乱码问题

问题描述 在敲陆文周的书《QT5开发及实例》的示例代码时&#xff0c;出现乱码&#xff0c;如下图所示 具体代码如下 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->treeWidget->clear();int groupSize 2;int ite…...

怎么提升搜狗网站排名

在当今数字化时代&#xff0c;网站排名对于品牌、企业以及个人都至关重要。而对于许多网站来说&#xff0c;搜狗搜索引擎是一个重要的流量来源。为了在搜狗上取得更好的排名&#xff0c;不仅需要优化网站内容&#xff0c;还需要巧妙运用一些工具和技巧。在本文中&#xff0c;我…...

搜索经典题——填充 9*9矩阵

题目&#xff1a;给定一个九行九列矩阵&#xff0c;填充矩阵元素&#xff0c;要求&#xff1a; 1、每一行每一列&#xff0c;每个小九宫格&#xff08;图片画粗的地方就是&#xff09;不能包含相同元素 2、每一行&#xff0c;每一列&#xff0c;每个小九宫格均会完整出现1-9的数…...

Vue待办事项(组件,模块化)

//html页面代码 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> <style> * { padding: 0; margin: 0; }…...

Vue中的组件

在应用程序的开发中&#xff0c;组件是不可缺少的。在Vue的使用中&#xff0c;同样也会用到组件。   vue组件的一般知识点&#xff1a;   1、组件的名字唯一&#xff1b;   2、组件以Html形式书写&#xff1b;   3、组件可以复用&#xff1b;   4、组件可以嵌套&…...

svg矢量图标在wpf中的使用

在wpf应用程序开发中&#xff0c;为支持图标的矢量缩放&#xff0c;及在不同分辨率下界面中图标元素的矢量无损缩放&#xff0c;所以常常用到svg图标&#xff0c;那么如果完 美的将svg图标运用到wpf日常的项目开发中呢&#xff0c;这里分享一下我的个人使用经验和详细步骤。 步…...

如何在云端加速缓存构建

缓存是指将某类数据存储起来以便以后重复使用的过程&#xff0c;它的运用在开发场景中非常普遍。类似于你习惯把最常用的调料放在厨房台面上&#xff0c;而不是橱柜里&#xff0c;这样你在准备大餐时就可以轻松取用。 但对于一个更为技术性、更精确的用例&#xff0c;比如像谷…...

JavaWeb-Cookie与Session

一、概念 是否还记得我们在HTTP概念中提到&#xff1a;HTTP的一大特点是无状态&#xff0c;这意味着多次HTTP请求之间是无法共享数据的。而在请求之间共享一些数据又是我们期望达到的效果。&#xff08;例如登录的记住我功能&#xff09;于是便有了会话跟踪技术&#xff0c;而…...

ZABBIX根据IP列表,主机描述,或IP子网批量创建主机的维护任务

有时候被ZABBIX监控的主机可能需要关机重启等维护操作,为了在此期间不触发告警,需要创建主机的维护任务,以免出现误告警 ZABBIX本身有这个API可供调用(不同版本细节略有不同,本次用的ZABBIX6.*),实现批量化建立主机的维护任务 无论哪种方式(IP列表,主机描述,或IP子网)创建维护…...

PMIS_ENT_STD

...

32 登录页组件

效果演示 实现了一个登录页面的样式&#xff0c;包括一个容器、左侧和右侧部分。左侧部分是一个背景图片&#xff0c;右侧部分是一个表单&#xff0c;包括输入框、复选框、按钮和忘记密码链接。整个页面的背景色为白色&#xff0c;容器为一个圆角矩形&#xff0c;表单为一个半透…...

Docker(一)简介和基本概念:什么是 Docker?用它会带来什么样的好处?

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; 一、简介 本章将带领你进入 Docker 的世界。 什么是 Docker&#xff1f; 用它会带来什么样的好处&#xff1f; 好吧&#xff0c;让我们带…...

【Linux】进程的概念 进程状态 进程优先级

Content 一、什么是进程1. 进程的概念2. 进程的描述 - 进程控制块&#xff08;PCB&#xff09;3. Linux下的进程 二、进程状态1. 教科书中的进程状态运行状态阻塞状态挂起状态 2. Linux下的进程状态R&#xff08;running&#xff09;- 运行状态S&#xff08;sleeping) - 睡眠状…...

Go语言热重载和优雅地关闭程序

Go语言热重载和优雅地关闭程序 我们有时会因不同的目的去关闭服务&#xff0c;一种关闭服务是终止操作系统&#xff0c;一种关闭服务是用来更新配置。 我们希望优雅地关闭服务和通过热重载重新加载配置&#xff0c;而这两种方式可以通过信号包来完成。 1、代码实现 package…...

Python实现两个列表相加的方法汇总

1. 使用 “” 运算符 通过 “” 运算符将两个列表相加&#xff0c;得到一个新的列表。例如&#xff1a; list1 [1, 2, 3] list2 [4, 5, 6] result list1 list2 print(result) # [1, 2, 3, 4, 5, 6]2. 使用 extend 方法 使用 extend 方法将一个列表中的元素逐个添加到另…...

debian12.4配置

文章目录 debian12.4配置概述笔记将非root用户添加到sudo组更换国内源配置ssh的客户端访问END debian12.4配置 概述 在虚拟机中装了一个debian12.4, 想配置ssh客户端连接, 出了问题. 配置乱了, 还好长了个心眼, 做了快照. 发现2个问题: debian12.4默认安装完, 有ssh, 先检查…...

linux切换root用户su - root和su root的区别

这里说一下login shell和 no login shell的区别 通过tty客户端登陆的shell就是login shell&#xff0c;通过在图形界面使用ctrlshiftt的方式新建的shell是no login shell login shell 主要读取两个配置文件/etc/profile和~/.bash_profile no login shell 读取的文件和顺序为&am…...

SQL Server Management Studio创建数据表

文章目录 一、建表注意事项1.1 数据类型1.2 建立数据表的基本SQL语法 二、实例说明2.1 创建数据表2.2 实例2 三、标识列和主键示例&#xff1a; 一、建表注意事项 1.1 数据类型 可以看这个去了解数据类型&#xff1a; 1.2 建立数据表的基本SQL语法 建立数据表的基本 SQL 语…...