【数据结构】C语言实现队列的相关操作
队列
队列是一种遵循先入先出规则的线性数据结构,是一种只允许在表的一端插入,在另一端删除的线性表
队尾 rear:插入端,线性表的表尾。
队头 front:删除端,线性表的表头
队列可以用数组或者链表实现
用数组实现队列时,由于队列采用头删尾插的操作方式,数组中队列的队头和队尾会不断的后移,如果到达数组末尾,那么队列就难以实现头删尾插的操作了,为了解决这种问题,采用循环队列的形式,让队头或队尾在越过数组尾部时,可以回到数组的头部然后继续操作,这种循环的形式可以通过取余操作来实现
用数组实现的队列又叫做“循环队列”
循环队列
表示
#define MAXSIZE 100 typedef struct Queue { int data[MAXSIZE]; int front; int rear; }Queue;
front:记录队头的位置
rear:记录队尾后第一个空间的位置
rear 记录的位置是由入队操作决定的
当 front == rear == 0 时,假设此时有一个元素 x 入队
则代码大致为
data[rear] == x
rear++
此时 rear 的位置就是队尾后第一个空间的位置
由于采用循环的操作形式,front 和 rear 更新都是通过取余的方式
front = (front + 1) % MAXSIZE
rear = (rear + 1) % MAXSIZE
当 front == rear 时,既有可能是队空,也有可能是队满,
为了区别这两种情况,要么少用一个存储空间,要么再设一个变量 size 来记录队列的大小
假设要求队列中能存储99个数据元素,则实际上需要100(MAXSIZE)个存储空间,数组的下标范围为0到99
队满情况表(少用一个存储空间)
front | rear | (rear + 1) % MAXSIZE |
---|---|---|
0 | 99 | 0 |
1 | 0 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
... | ||
98 | 97 | 98 |
99 | 98 | 99 |
于是有
队空:front == rear
队满:(rear + 1) % MAXSIZE == front
初始化
void init(Queue* Q) { int i = 0; for (i = 0; i < MAXSIZE; i++) { Q->data[i] = 0; } Q->front = 0; Q->rear = 0; }
或者在主函数中
Queue Q = { {0},0,0 };
判断队空
int isEmpty(Queue* Q) { if (Q->front == Q->rear) { return 1; } else { return 0; } }
判断队满
int isFull(Queue* Q) { if ((Q->rear + 1) % MAXSIZE == Q->front) { return 1; } else { return 0; } }
入队
void in(Queue* Q, int x) { if (isFull(Q) == 1) { return; } else { Q->data[Q->rear] = x; Q->rear = (Q->rear + 1) % MAXSIZE; } }
出队
int out(Queue* Q) { if (isEmpty(Q) == 1) { exit(0); } else { int x = Q->data[Q->front]; Q->front = (Q->front + 1) % MAXSIZE; return x; } }
求队列长度
int get_length(Queue* Q) { int length = (Q->rear - Q->front + MAXSIZE) % MAXSIZE; return length; }
清空
void clear(Queue* Q) { Q->front = 0; Q->rear = 0; }
链式队列
用链表表示的队列简称为链队列
链队列带头结点,有头指针和尾指针,头指针指向头结点(不是首元结点),尾指针指向最后一个结点
队头是首元结点,队尾是尾结点,
在单链表中,用链表的头指针来表示链表,在给结构体自定义名字时,除了一个 QNode 还有一个指针 LinkList,该指针就是头指针,由于用头指针表示链表,所以名字就叫 LinkList
在链队中,同样是用头指针来表示链队,但是除了一个头指针,链队中还有一个尾指针,为了方便操作,又定义了一个结构体来存放头指针和尾指针,在对该结构体自定义名字时,名字就叫 LinkQueue
目前我的理解是,其实链表就是一个指针,没什么花里胡哨的
表示
typedef struct QueueNode { int data; struct QueueNode* next; }QueueNode; typedef struct QueuePtr { QueueNode* front; QueueNode* rear; }LinkQueue;
当 front == rear 时,表示链队为空
初始化
void init(LinkQueue* Q) { Q->front = (QueueNode*)malloc(sizeof(QueueNode)); if (Q->front == NULL) { return; } Q->rear = Q->front; Q->front->next = NULL; }
判断队空
int isEmpty(LinkQueue* Q) { if (Q->front == Q->rear) { return 1; } else { return 0; } }
入队
void in(LinkQueue* Q, int x) { QueueNode* p = (QueueNode*)malloc(sizeof(QueueNode)); if (p == NULL) { return; } p->data = x; p->next = NULL; Q->rear->next = p; Q->rear = p; }
出队
int out(LinkQueue* Q) { if (isEmpty(Q) == 1) { exit(0); } QueueNode* p = Q->front->next; int x = p->data; if (Q->front->next == Q->rear) { Q->rear = Q->front; } Q->front->next = p->next; free(p); p = NULL; return x; }
出队时,注意当链队中除头结点外只剩一个结点时的情况,此时需要改变尾指针的指向
求队长
int get_length(LinkQueue* Q) { QueueNode* p = Q->front->next; int count = 0; while (p != NULL) { p = p->next; count++; } return count; }
取队头
int get_front(LinkQueue* Q) { if (isEmpty(Q) == 1) { exit(0); } int x = Q->front->next->data; return x; }
清空
遵循队列头删尾插操作原则的清空
void clear(LinkQueue* Q) { QueueNode* p = Q->front->next; while (p != NULL) { QueueNode* pFree = p; Q->front->next = p->next; free(pFree); pFree = NULL; p = Q->front->next; } Q->rear = Q->front; }
遍历清空
void clear(LinkQueue* Q) { QueueNode* p = Q->front->next; while (p != NULL) { QueueNode* pFree = p; p = p->next; free(pFree); pFree = NULL; } Q->front->next = NULL; Q->rear = Q->front; }
销毁
void destroy(LinkQueue* Q) { clear(Q); free(Q->front); Q->front = NULL; Q->rear = NULL; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了