队列的知识总结归纳
- 一.队列的基本概念
- 二.循环队列的顺序存储常见的基本操作以及详细图解
- 1.队列的顺序存储结构类型定义
- 2.初始化队列
- 初始化队列示意图
- 3.判断队空
- 4.判断队列是否满的三种方法
- 图示
- 5.入队或进队
- 入队的示意图
- 6出队或退队
- 出队的图示
- 三. 队列的链式存储结构
- 四. 链式队列的基本操作
- 1.队列链式存储类型描述
- 2.初始化链式队列
- 3.链式队列判断队列是否为空
- 4.链式队列入队操作
- 5.链式队列的出队操作
- 五. 双端队列
- 六.总结
一.队列的基本概念
队列简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除,向队列中插入元素叫做入队,进队,向队列中删除元素叫做出队或离队.这和我们日常排队打饭是一样的,都遵循一个先进先出的特性
二.循环队列的顺序存储常见的基本操作以及详细图解
1.队列的顺序存储结构类型定义
#define _CRT_SECURE_NO_WARNINGS 1
#define MAX_SIZE 100
#define Elemtype int
typedef struct QNode
{
Elemtype data[MAX_SIZE];
int front;//队头指针
int rear;//队尾指针
}SqQueue;
2.初始化队列
void InitQueue(SqQueue* SQ)
{
SQ->front = SQ->rear = 0;
}
初始化队列示意图
3.判断队空
bool QueueEmpty(SqQueue* SQ)
{
if (SQ->front == SQ->rear)//队空
{
return true;
}
return false;
}
他就是根据上面的初始化状态进行队列的判空操作,从上面图示显然可以知道,当front和rear都等于0的时候为空队列
4.判断队列是否满的三种方法
方法一:
通过浪费一个存储空间来区别队满队空的状态
图示
bool QueueFull(SqQueue* SQ)
{
if ((SQ->rear + 1) % MAX_SIZE == SQ->front)
{
return true;
}
return false;
}
方法二:
在定义队列的存储类型的时候,创建一个length的变量,每次插入元素让length++,每次删除元素让length–,知道length == MAX_SIZE的时候来得出队列是否为满队状态
方法三:
在定义队列的存储类型时,只需要定义一个变量flag,入队操作flag =1,出队操作,flag = 0;让其区分在front==rear的时候,此时的flag状态是1的话就说明队满了
5.入队或进队
在入队的时候我们首先要判断队列是否满的情况,以免出现内存上溢,后面使用rear = (rear+1)%MAX_SIZE是为了尽可能不浪费内存空间,让进行出队后空出来的空间能够二次使用,使得整个队列呈一个环状的空间也可将其称为循环队列
bool PushQueue(SqQueue* SQ,int x)
{
if ((SQ->rear + 1) % MAX_SIZE == SQ->front)
{
return true;//队满
}
SQ->data[SQ->rear] = x;
SQ->rear = (SQ->rear + 1) % MAX_SIZE;
return false;
}
入队的示意图
6出队或退队
在出队的时候,我们首先需要判断队列是否为空队列,其次,为了能够充分利用开辟的空间,我们的front指针要以环状的遍历方式让元素出队
代码实现
bool PopQueue(SqQueue* SQ, int* x)
{
if (SQ->front == SQ->rear)
{
return false;
}
*x = SQ->data[SQ->front];
SQ->front = (SQ->front + 1) % MAX_SIZE;//使得头指针对一个环状的队列进行出队操作
return true;
}
出队的图示
三. 队列的链式存储结构
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点
四. 链式队列的基本操作
1.队列链式存储类型描述
#define _CRT_SECURE_NO_WARNINGS 1
typedef struct LinkNode
{
struct LinkNode* next;
int data;
}LNode;
typedef struct
{
LNode* front;//队头指针
LNode* rear;//队尾指针
}*LinkQueue;
2.初始化链式队列
建立头结点,让front 和 rear 指针都指向头结点
void InitQueue(LinkQueue& LQ)
{
LQ->front = LQ->rear = (LNode*)malloc(sizeof(LNode));//建立头结点
LQ->front = NULL;//初始化为空
}
3.链式队列判断队列是否为空
图解
只需要判断队头和队尾指针是否指向的是否为同一结点
bool IsEmpty(LinkQueue& LQ)
{
if (LQ->front == LQ->rear)
{
return true;//队空
}
else
return false;//非空
}
4.链式队列入队操作
链式队列与前面的队列顺序存储结构有所不同,顺序存储结构的队列在入队时需要进行队满的一个判断,而在链式存储结构中基本不存在空间不够用的情况,因为我们可以根据自己的需求进行空间的开辟,但队列的特性是不变的,依旧是遵循先进先出。
图示
**注意:**在入队第一个元素时,需要让我们的front指针指向链表的第一个带元素结点,但是如果我们初始化的时候不创建头结点的话,就不需要特殊处理第一个元素的入队操作,而不创建头结点,判断队空就是在初始化队列的时候让front和rear赋值为空指针,就当队头队尾指针都等于NULL的时候队列为空队
void PushQueue(LinkQueue& LQ,int input)
{
LNode* s = (LNode*)malloc(sizeof(LNode));//创建新节点插入到队列中
s->data = input;
if (LQ->front == LQ->rear)
{
LQ->front = s;
}
LQ->rear->next = s;
s->next = NULL;
LQ->rear = s;
}
5.链式队列的出队操作
图示:
删除结点为队尾结点
代码实现:
bool PopQueue(LinkQueue& LQ, int* output)
{
if (LQ->front == LQ->rear)
{
return false;//队空
}
LNode* p = LQ->front->next;
*output = p->data;
LQ->front->next = p->next;//让结点p出队
if (p == LQ->rear)//p为队列的队尾,删除之后让rear指针继续跟front指向同一结点使得达到空队的效果
{
LQ->rear = LQ->front;
}
free(p);//释放结点p,防止内存泄露
}
五. 双端队列
双端队列是指允许两端进行入队出队操作的队列其元素逻辑结构仍是线性结构
除了以上这种双端队列还有变种的受限的双端队列
一.一端可以进行插入删除,而另一端只能进行插入
二.一端可进行插入删除,另一端仅可以删除
限制双端队列和上面的队列使用只需要考虑受限的情况就好了,只需要了解队列出队和进队的操作的不同,入队和退队的受限不同肯定也会对元素的出队入队的顺序也会有其他方式
六.总结
队列是一种先进先出(FIFO)的数据结构,它的主要操作包括入队(enqueue)和出队(dequeue)。队列通常用于存储需要按顺序处理的元素,例如打印机任务、消息队列等。
队列可以通过数组或链表实现。使用数组实现的队列通常需要考虑队列满的情况,可以用循环队列解决。使用链表实现的队列则不需要考虑队列满的情况,但需要注意空队列的情况。
队列的时间复杂度为O(1)。入队和出队都只需要对队首或队尾进行操作,因此时间复杂度为常数级别。但需要注意的是,数组实现的队列在出队时需要移动元素,因此最坏情况下时间复杂度可能为O(n)。
队列还有一些常见的变种,例如双端队列(deque)、优先队列(priority queue),要注意他们的进队和退队的方式,它们都是在队列的基础上进行了扩展和优化。