队列(Queue)是一种先进先出(FIFO,First-In-First-Out)的线性数据结构。它类似于现实生活中的排队,新元素被添加到队尾,而从队首移除元素。
1. 队列的基本操作:
- Enqueue (入队): 将一个元素添加到队列的尾部。
- Dequeue (出队): 从队列的头部移除一个元素,并返回该元素的值。
- Front (队首): 返回队首元素的值,但不移除它。
- Rear (队尾): 返回队尾元素的值,但不移除它。
- IsEmpty (判空): 检查队列是否为空。
- IsFull (判满): 检查队列是否已满(对于固定大小的队列)。
- Size (大小): 返回队列中元素的数量。
2. 队列的实现方式:
C语言中,队列通常可以通过两种方式实现:
- 数组实现 (顺序队列): 使用数组来存储队列元素,需要维护队首和队尾指针。
- 链表实现 (链式队列): 使用链表来存储队列元素,每个节点包含数据和指向下一个节点的指针。
3. 数组实现 (顺序队列):
3.1 结构体定义:
#define MAX_SIZE 100 // 定义队列的最大容量
typedef struct {
int data[MAX_SIZE]; // 存储队列元素的数组
int front; // 队首指针,指向队首元素
int rear; // 队尾指针,指向队尾元素的下一个位置
} Queue;
3.2 初始化队列:
void initQueue(Queue *q) {
q->front = 0;
q->rear = 0;
}
3.3 判空:
int isEmpty(Queue *q) {
return q->front == q->rear;
}
3.4 判满:
int isFull(Queue *q) {
return (q->rear + 1) % MAX_SIZE == q->front; // 循环队列的判满条件
}
3.5 入队:
int enqueue(Queue *q, int value) {
if (isFull(q)) {
printf("Queue is full!\n");
return 0; // 入队失败
}
q->data[q->rear] = value;
q->rear = (q->rear + 1) % MAX_SIZE; // 循环队列的队尾指针更新
return 1; // 入队成功
}
3.6 出队:
int dequeue(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0; // 出队失败
}
*value = q->data[q->front];
q->front = (q->front + 1) % MAX_SIZE; // 循环队列的队首指针更新
return 1; // 出队成功
}
3.7 获取队首元素:
int front(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0;
}
*value = q->data[q->front];
return 1;
}
3.8 获取队尾元素:
int rear(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0;
}
*value = q->data[(q->rear - 1 + MAX_SIZE) % MAX_SIZE]; // 注意循环队列的队尾元素位置
return 1;
}
3.9 循环队列:
为了避免“假溢出”现象(即队列还有空间,但 rear 指针已经到达数组末尾),通常使用循环队列。循环队列通过取模运算 (%) 来实现队首和队尾指针的循环移动。
4. 链表实现 (链式队列):
4.1 结构体定义:
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct {
Node *front;
Node *rear;
} Queue;
4.2 初始化队列:
void initQueue(Queue *q) {
q->front = NULL;
q->rear = NULL;
}
4.3 判空:
int isEmpty(Queue *q) {
return q->front == NULL;
}
4.4 入队:
int enqueue(Queue *q, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
return 0; // 入队失败
}
newNode->data = value;
newNode->next = NULL;
if (isEmpty(q)) {
q->front = newNode;
q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
return 1; // 入队成功
}
4.5 出队:
int dequeue(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0; // 出队失败
}
Node *temp = q->front;
*value = temp->data;
q->front = q->front->next;
if (q->front == NULL) {
q->rear = NULL; // 如果队列为空,则 rear 也需要置为 NULL
}
free(temp);
return 1; // 出队成功
}
4.6 获取队首元素:
int front(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0;
}
*value = q->front->data;
return 1;
}
4.7 获取队尾元素:
int rear(Queue *q, int *value) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return 0;
}
*value = q->rear->data;
return 1;
}
5. 完整示例 (链表实现):
#include <stdio.h>
#include <stdlib.h>
// ... (链表实现的结构体定义和函数) ...
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
int value;
if (front(&q, &value)) {
printf("Front element: %d\n", value);
}
if (rear(&q, &value)) {
printf("Rear element: %d\n", value);
}
while (!isEmpty(&q)) {
if (dequeue(&q, &value)) {
printf("Dequeued element: %d\n", value);
}
}
return 0;
}
6. 数组实现 vs 链表实现:
特性 | 数组实现 (顺序队列) | 链表实现 (链式队列) |
---|---|---|
内存分配 | 静态分配 | 动态分配 |
空间利用率 | 可能浪费空间 | 更有效利用空间 |
插入/删除 | 可能需要移动元素 | 效率更高 |
大小限制 | 固定大小 | 大小可变 |
实现复杂度 | 相对简单 | 相对复杂 |
总结:
- 数组实现: 简单,但受限于预定义的大小,可能导致空间浪费或溢出。
- 链表实现: 灵活,可以动态调整大小,空间利用率高,插入和删除操作效率更高,但实现相对复杂,需要处理指针。
选择哪种实现方式取决于具体应用场景的需求。如果队列大小已知且固定,