✨ 写在前面
本人是一位计算机的大一新生,也是经常在网上查找文章进行学习,经常有时候会懒得做笔记了,就在网上找相应写的比较好的文章收藏便于复习,但是有时候一个知识点我需要收集好几个文章的信息并且内容多有重复,不便于整理,于是萌发了自己写文章的念头。目的是能督促自己对知识点进行总结整理,加深理解,以自己能理解的方式记录下来,也希望能帮助到向我一样努力积极勤奋的计算机新手,欢迎大家讨论交流!
如果有哪里理解的不对的地方,欢迎指正!🥰
目录
一、引言
队列是一种常见的受限数据结构,它按照先进先出的原则对元素进行操作。其中进入队列的一端叫队尾(rear),出队列的一端叫队头(front)
队列
二、队列的特点
-
先进先出原则:队列中的元素按照进入队列的顺序依次出队。
-
头尾操作:队列只有队头和队尾两个操作点,元素的添加和删除都在这两个位置进行。
三、队列的存储结构
队列有三种存储结构,包括顺序队列、循环队列和链式队列,其中顺序队列容易遇到假溢出的问题,很少使用,下面的代码将主要介绍循环队列和链式队列。
值得注意的是,在操作中头尾指针都是往同一个方向移动的,并且尾指针在头指针前面,不要混淆哦。
(一)顺序队列
线性队列操作如下图所示:
顺序队列
由图可见,无论是入队还是出队的操作,头尾指针都是向同一个方向移动的,这样在不断地入队出队之后,头尾指针不断后移,直至超出内存,而刚开始使用的内存则不能再被使用,导致浪费,这就是假溢出。
解决假溢出的方法就是使用循环队列。
(二)循环队列
循环队列是将顺序队列的头和尾连接起来,这样在头或尾指针超出内存时,会重新指向第一个位置,其原理如下图所示:
循环队列
在没有超出内存范围的情况下,对于超出内存的指针下标只需要将其对空间内存大小取模(mod)就可以指向初始的位置了;
(三)链式队列
使用链式队列离散化存储,不需要初始分配足够大的内存,不会造成内存浪费。
队头指针指向头节点,队尾指针指向末节点。
四、队列的应用场景
-
任务排队:例如操作系统中的进程调度、打印机任务队列等。
-
消息传递:在网络通信中,消息可以按照发送顺序进行处理。
-
数据缓冲区:用于临时存储数据,例如视频播放器的缓冲区。
五、C 代码实现队列
(一)循环队列
1、结构体定义
typedef struct {
int queue[QUEUE_MAXSIZE];
int rear; //队尾指针
int front; //队头指针
int size; //计数器
}SeqQueue;
2、基本操作代码
//初始化队列
void Queue_Init(SeqQueue* Q)
{
Q->front = Q->rear = 0;
Q->size = 0;
}
//判断队列是否为空
int Queue_Empty(SeqQueue Q)
{
return Q.size == 0;
}
//入队列
int Queue_Add(SeqQueue* Q, int x)
{
if (Q->size == QUEUE_MAXSIZE)
{
printf("队列已满,无法入队!\n");
return -1;
}
Q->queue[ (Q->rear++) % QUEUE_MAXSIZE] = x;
Q->size++;
return x;
}
//出队列
int Queue_Pop(SeqQueue* Q)
{
if (Queue_Empty(*Q))
{
printf("队列已空,无法出队!\n");
return -1;
}
Q->size--;
return Q->queue[ (Q->front++) % QUEUE_MAXSIZE];
}
//取队列头元素
int Queue_Front(SeqQueue Q)
{
if (Queue_Empty(Q))
{
printf("队列为空!\n");
return -1;
}
else
{
return Q.queue[Q.front];
}
}
//打印队列中所有元素
void Queue_Print(SeqQueue Q)
{
while (!Queue_Empty(Q))
{
printf("%d ", Queue_Pop(&Q));
}
printf("\n");
}
3、测试一下
(二)链式队列
front指向链表头,rear指向链表尾,入队rear后移,出队front后移。
1、结构体定义
typedef struct LinkQueue {
int data;
struct LinkQueue* next; //节点结构体
}LinkQueue;
typedef struct {
LinkQueue* rear, * front; //指向头和尾的指针
}QueuePtr;
2、函数实现
变化一:与我一贯的操作(链表头不存放数据)不同,这里我用链表头存放了数据,主要是为了遵循上文的作风,即front指向第一个元素,这在细节上还是有区别的。
变化二:以上的循环队列的尾指针是指向队列的下一个位置的,在链表这里末尾的下一个位置为空了,所以rear指向的是最后一个节点。
//初始化链式队列
void LinkQueue_Init(QueuePtr* pq)
{
pq->front = pq->rear = NULL;
}
//判断链式队列是否为空
int LinkQueue_Empty(QueuePtr pq)
{
return pq.front == NULL && pq.rear == NULL;
}
//入队列
int LinkQueue_Add(QueuePtr* pq,int x)
{
LinkQueue* new = (LinkQueue*)malloc(sizeof(LinkQueue));
if (new == NULL)
{
printf("内存分配失败!\n");
return -1;
}
else
{
new->data = x;
new->next = NULL;
if (LinkQueue_Empty(*pq)) //如果队列为空,则两个指针都指向第一个节点
{
pq->front = new;
pq->rear = new;
}
else //否则插入到链表尾部
{
pq->rear->next = new;
pq->rear = new;
}
return x;
}
}
//出队列
int LinkQueue_Pop(QueuePtr* pq)
{
if (LinkQueue_Empty(*pq))
{
printf("队列已空!\n");
return -1;
}
LinkQueue* dep = pq->front;
int x = dep->data;
pq->front = dep->next;
//这里值得注意!由于我们每次出队操作都是移动头指针向后,当只有一个元素的时候,front后移指向了NULL,但是rear没有变化,仍然指向即将释放的内容,导致程序出错,因此需要作出判断。
if (pq->front == NULL)
{
pq->rear = NULL;
}
free(dep);
return x;
}
//取队列头元素
int LinkQueue_Top(QueuePtr pq)
{
if (LinkQueue_Empty(pq))
{
printf("队列已空!\n");
return -1;
}
else
{
return pq.front->data;
}
}
//打印链式队列
void LinkQueue_Print(QueuePtr* pq)
{
while (!LinkQueue_Empty(*pq))
{
printf("%d ", LinkQueue_Pop(pq));
}
printf("\n");
}
六、小结
队列作为一种基本的数据结构,具有简单、高效的特点,适用于各种任务排队、消息传递和数据缓冲区等场景。
最后感谢你观看完我的文章,如果文章对你有帮助,可以点赞收藏评论,这是对作者最好的鼓励!不胜感激🥰