上篇文章我们讲了栈,知道了栈的结构与应用场景,接下来这篇文章将带你们走入栈的“好朋友”队列,一起学习队列的结构与应用场景。废话少说,赶快系上安全带,我们开始发车~~~
1. 什么是队列?
队列(Queue)是一种基础的线性数据结构,遵循先进先出(FIFO, First In First Out)的原则。就像生活中的排队场景,先到的人先服务,后到的人排在队伍后面,等待前面的服务完成后才能轮到自己。
队列的核心操作包括:
- 入队(Enqueue):将元素添加到队尾。
- 出队(Dequeue):将元素从队头移除。
- 查看队头元素(Peek/Front):获取队头元素但不移除它。
2. 队列的结构
队列是一种受限的线性表,其操作限制为:
- 只能从一端(队尾)添加元素。
- 只能从另一端(队头)移除元素。
队列可以通过两种方式实现:
- 顺序队列:基于数组实现,固定大小。
- 链式队列:基于链表实现,动态大小。
3. 队列的实现
我们分别用数组和链表实现队列,并简要分析它们的特点:
(1)顺序队列
顺序队列基于数组实现,但在数组中频繁的插入和删除操作可能会导致效率低下,因此需要通过循环队列来优化。
循环队列的定义和基本操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义循环队列结构
typedef struct {
int *data; // 动态数组
int front; // 队头索引
int rear; // 队尾索引
int size; // 队列当前大小
int capacity; // 队列容量
} CircularQueue;
// 初始化队列
CircularQueue* createQueue(int capacity) {
CircularQueue* queue = (CircularQueue*)malloc(sizeof(CircularQueue));
queue->data = (int*)malloc(sizeof(int) * capacity);
queue->front = 0;
queue->rear = -1;
queue->size = 0;
queue->capacity = capacity;
return queue;
}
// 判断队列是否为空
bool isEmpty(CircularQueue* queue) {
return queue->size == 0;
}
// 判断队列是否已满
bool isFull(CircularQueue* queue) {
return queue->size == queue->capacity;
}
// 入队操作
void enqueue(CircularQueue* queue, int value) {
if (isFull(queue)) {
printf("队列已满,无法插入元素。\n");
return;
}
queue->rear = (queue->rear + 1) % queue->capacity; // 循环队列
queue->data[queue->rear] = value;
queue->size++;
}
// 出队操作
int dequeue(CircularQueue* queue) {
if (isEmpty(queue)) {
printf("队列为空,无法移除元素。\n");
return -1;
}
int value = queue->data[queue->front];
queue->front = (queue->front + 1) % queue->capacity; // 循环队列
queue->size--;
return value;
}
// 查看队头元素
int peek(CircularQueue* queue) {
if (isEmpty(queue)) {
printf("队列为空,无队头元素。\n");
return -1;
}
return queue->data[queue->front];
}
// 释放队列
void freeQueue(CircularQueue* queue) {
free(queue->data);
free(queue);
}
测试循环队列
int main() {
CircularQueue* queue = createQueue(5);
enqueue(queue, 10);
enqueue(queue, 20);
enqueue(queue, 30);
printf("队头元素: %d\n", peek(queue));
printf("出队元素: %d\n", dequeue(queue));
printf("队头元素: %d\n", peek(queue));
freeQueue(queue);
return 0;
}
(2)链式队列
链式队列基于链表实现,能够动态扩展队列的大小,避免了顺序队列中因容量固定导致的溢出问题。
链式队列的定义和基本操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义链表节点
typedef struct Node {
int data; // 数据域
struct Node* next; // 指向下一个节点
} Node;
// 定义链式队列结构
typedef struct {
Node* front; // 队头指针
Node* rear; // 队尾指针
} LinkedQueue;
// 初始化队列
LinkedQueue* createQueue() {
LinkedQueue* queue = (LinkedQueue*)malloc(sizeof(LinkedQueue));
queue->front = NULL;
queue->rear = NULL;
return queue;
}
// 判断队列是否为空
bool isEmpty(LinkedQueue* queue) {
return queue->front == NULL;
}
// 入队操作
void enqueue(LinkedQueue* queue, int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = queue->rear = newNode; // 第一个元素
} else {
queue->rear->next = newNode; // 链接新节点
queue->rear = newNode; // 更新队尾
}
}
// 出队操作
int dequeue(LinkedQueue* queue) {
if (isEmpty(queue)) {
printf("队列为空,无法移除元素。\n");
return -1;
}
Node* temp = queue->front;
int value = temp->data;
queue->front = temp->next;
if (queue->front == NULL) { // 队列为空时更新队尾
queue->rear = NULL;
}
free(temp); // 释放节点
return value;
}
// 查看队头元素
int peek(LinkedQueue* queue) {
if (isEmpty(queue)) {
printf("队列为空,无队头元素。\n");
return -1;
}
return queue->front->data;
}
// 释放队列
void freeQueue(LinkedQueue* queue) {
while (!isEmpty(queue)) {
dequeue(queue);
}
free(queue);
}
测试链式队列
int main() {
LinkedQueue* queue = createQueue();
enqueue(queue, 10);
enqueue(queue, 20);
enqueue(queue, 30);
printf("队头元素: %d\n", peek(queue));
printf("出队元素: %d\n", dequeue(queue));
printf("队头元素: %d\n", peek(queue));
freeQueue(queue);
return 0;
}
4. 顺序队列和链式队列的对比
特性 | 顺序队列 | 链式队列 |
---|---|---|
存储方式 | 数组,固定大小 | 链表,动态大小 |
动态扩展 | 需要手动扩展容量 | 内存动态分配,自动扩展 |
实现复杂度 | 较简单 | 较复杂 |
5. 队列的应用场景
队列以其先进先出的特点,在许多实际场景中有重要应用:
- 任务调度:操作系统中的任务管理通常使用队列来调度进程。
- 广度优先搜索(BFS):图算法中使用队列来实现层次遍历。
- 消息队列:在分布式系统中,队列用来在多个服务之间传递消息。
- 打印机队列:处理打印任务时,打印机通常使用队列按顺序输出任务。
说在最后
队列是一种简单但高效的数据结构,其特性是先进先出,在计算机科学中扮演着重要角色。通过这篇文章,希望你不仅了解了队列的结构和特点,还掌握了用C语言实现顺序队列和链式队列的方法。
如果有其它想法欢迎评论区交流~~~