队列的概念和结构
队列(Queue)是一种特殊的线性表,它具有先进先出(FIFO, First In First Out)的特性。在队列中,数据的插入操作被称为入队(Enqueue),数据元素的插入只能在队列的一端进行,这一端通常被称为队尾(Rear);相应地,数据的删除操作被称为出队(Dequeue),数据元素的删除只能在队列的另一端进行,这一端被称为队首(Front)。
队列的存储结构主要分为两种:链式存储和顺序存储。接下来讲解的是链式储存。
如图所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
队列的链式储存实现
队列的链式存储结构为单链表,在链式存储队列中,因为将数据入队是从队尾插入所以可能需要从头节点开始遍历到尾节点,时间复杂度较高,为O(n)。但为了降低时间复杂度可以额外开辟一个结构体来存储队列的头指针和尾指针,这样可以有效降低时间复杂度。此外,如果想要知道队列里有效数据的个数,可以往队列结构体里再存储一个size变量记录队列中有效数据的个数。
以下是用链表实现队列的代码:
头函数准备:
#pragma once #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<assert.h>
定义:
typedef struct s_Node { int data; struct s_Node* next; }sNode; typedef struct queue_LK { sNode* front; sNode* rear; int size; }queueLK;
初始化队列:
void init_queue(queueLK* Q) { Q->front = NULL; Q->rear = NULL; Q->size = 0; }
队列判空:
bool empty_queue(queueLK* Q) { assert(Q); return(Q->front == NULL && Q->rear == NULL); }
入队:
void push_queue(queueLK* Q, int num) { assert(Q); sNode* newnode = (sNode*)malloc(sizeof(sNode)); if (newnode == NULL) { printf("error"); exit(1); } newnode->next = NULL; newnode->data = num; if (Q->front == NULL) { Q->front = newnode; Q->rear = newnode; } else { Q->rear->next = newnode; Q->rear = newnode; } Q->size++; }
出队:
void pop_queue(queueLK* Q) { assert(Q); assert(!empty_queue(Q)); if (Q->front == Q->rear) { free(Q->front); Q->front = Q->rear = NULL; } else { sNode* emp = Q->front; Q->front = Q->front->next; free(emp); } Q->size--; }
取队头元素:
int front_queue(queueLK* Q) { assert(Q); assert(!empty_queue(Q)); return(Q->front->data); }
取队尾元素:
int rear_queue(queueLK* Q) { assert(Q); assert(!empty_queue(Q)); return(Q->rear->data); }
有效长度:
int size_queue(queueLK* Q) { assert(Q); return(Q->size); }
打印队列数据:
void print_queue(queueLK* Q) { assert(Q); assert(!empty_queue(Q)); sNode* current = Q->front; while (current) { printf("%d ", current->data); current = current->next; } printf("\n"); }
销毁:
void destory_queue(queueLK* Q) { assert(Q); assert(!empty_queue(Q)); sNode* p = Q->front; while (p) { sNode* next = p->next; free(p); p = next; } Q->front = Q->rear = NULL; Q->size = 0; }
测试用例如下所示:
int main() { queueLK* Q = (queueLK*)malloc(sizeof(queueLK)); assert(Q); init_queue(Q); push_queue(Q, 1); push_queue(Q, 2); push_queue(Q, 3); pop_queue(Q); print_queue(Q); printf("队列的长度为:%d", size_queue(Q)); destory_queue(Q); free(Q); return 0; }
结果:
优点:
无假溢出问题:链式存储队列通过指针连接节点,理论上队列的大小只受限于系统内存的大小,不存在假溢出问题。
动态扩容方便:在链式存储队列中,当需要增加新的元素时,只需动态地分配新的节点即可,无需进行大量数据的移动。
空间利用率灵活:每个节点可以只存储必要的数据和指针,空间利用率较为灵活。
缺点:
空间利用率相对较低:由于每个节点除了存储数据外,还需要存储指针,因此相比于顺序存储,链式存储的空间利用率较低。
访问效率低:在链式存储队列中,访问某个特定位置的元素可能需要从头节点开始遍历,时间复杂度较高,为O(n)。但为了降低时间复杂度可以额外开辟一个结构体来存储队列的头指针和尾指针,这样可以有效降低时间复杂度,变为O(1),但同时也提高了内存开销。
内存管理开销:动态分配和释放节点需要额外的内存管理开销。