队列是一种遵循先进先出(FIFO)原则的线性数据结构。它在许多场景中都有广泛应用,如任务调度、缓冲区管理等。本文将介绍三种常见的队列类型:普通队列、双端队列和优先队列,并提供它们在C语言中的实现。
1. 普通队列
普通队列是最基本的队列类型,只允许在一端(称为队尾)插入元素,在另一端(称为队首)删除元素。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
} Queue;
void initQueue(Queue *q) {
q->front = -1;
q->rear = -1;
}
int isEmpty(Queue *q) {
return q->front == -1;
}
int isFull(Queue *q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
void enqueue(Queue *q, int value) {
if (isFull(q)) {
printf("Queue is full\n");
return;
}
if (isEmpty(q)) {
q->front = 0;
}
q->rear = (q->rear + 1) % MAX_SIZE;
q->items[q->rear] = value;
}
int dequeue(Queue *q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
int item = q->items[q->front];
if (q->front == q->rear) {
// Last element
q->front = -1;
q->rear = -1;
} else {
q->front = (q->front + 1) % MAX_SIZE;
}
return item;
}
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
printf("Dequeued: %d\n", dequeue(&q));
printf("Dequeued: %d\n", dequeue(&q));
return 0;
}
/* 运行结果:
Dequeued: 10
Dequeued: 20
*/
这个实现使用了循环数组来避免假溢出问题,使得队列可以更有效地利用数组空间。
应用场景
- 进程调度:操作系统使用队列来管理等待执行的进程。
- 缓冲区管理:在数据传输中,队列用于管理数据缓冲区,如打印机任务队列。
- 广度优先搜索(BFS):在图的遍历算法中,BFS 使用队列来跟踪待访问的节点。
- 消息队列:在分布式系统中,消息队列用于在不同组件之间异步传递消息。
2. 双端队列
双端队列(Deque)允许在队列的两端进行插入和删除操作,提供了更大的灵活性。
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *prev;
struct Node *next;
} Node;
typedef struct {
Node *front;
Node *rear;
} Deque;
void initDeque(Deque *d) {
d->front = d->rear = NULL;
}
int isEmpty(Deque *d) {
return d->front == NULL;
}
void insertFront(Deque *d, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->prev = NULL;
newNode->next = d->front;
if (isEmpty(d)) {
d->rear = newNode;
} else {
d->front->prev = newNode;
}
d->front = newNode;
}
void insertRear(Deque *d, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL;
newNode->prev = d->rear;
if (isEmpty(d)) {
d->front = newNode;
} else {
d->rear->next = newNode;
}
d->rear = newNode;
}
int deleteFront(Deque *d) {
if (isEmpty(d)) {
printf("Deque is empty\n");
return -1;
}
Node *temp = d->front;
int value = temp->data;
d->front = d->front->next;
if (d->front == NULL) {
d->rear = NULL;
} else {
d->front->prev = NULL;
}
free(temp);
return value;
}
int deleteRear(Deque *d) {
if (isEmpty(d)) {
printf("Deque is empty\n");
return -1;
}
Node *temp = d->rear;
int value = temp->data;
d->rear = d->rear->prev;
if (d->rear == NULL) {
d->front = NULL;
} else {
d->rear->next = NULL;
}
free(temp);
return value;
}
int main() {
Deque d;
initDeque(&d);
insertFront(&d, 10);
insertRear(&d, 20);
insertFront(&d, 5);
printf("Delete front: %d\n", deleteFront(&d));
printf("Delete rear: %d\n", deleteRear(&d));
return 0;
}
/* 运行结果:
Delete front: 5
Delete rear: 20
*/
这个实现使用了双向链表,允许在两端高效地进行插入和删除操作。
应用场景
- 工作窃取调度算法:在并行计算中,双端队列用于实现工作窃取算法。
- 撤销操作:在文本编辑器中,双端队列可以用来实现撤销和重做功能。
- 滑动窗口问题:在处理固定大小的滑动窗口时,双端队列很有用。
- 回文检查:双端队列可以有效地用于检查一个字符串是否为回文。
3. 优先队列
优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。这里我们使用最大堆来实现最大优先队列。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int heap[MAX_SIZE];
int size;
} PriorityQueue;
void initPQ(PriorityQueue *pq) {
pq->size = 0;
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void heapifyUp(PriorityQueue *pq, int index) {
int parent = (index - 1) / 2;
if (index > 0 && pq->heap[index] > pq->heap[parent]) {
swap(&pq->heap[index], &pq->heap[parent]);
heapifyUp(pq, parent);
}
}
void heapifyDown(PriorityQueue *pq, int index) {
int largest = index;
int left = 2 * index + 1;
int right = 2 * index + 2;
if (left < pq->size && pq->heap[left] > pq->heap[largest]) {
largest = left;
}
if (right < pq->size && pq->heap[right] > pq->heap[largest]) {
largest = right;
}
if (largest != index) {
swap(&pq->heap[index], &pq->heap[largest]);
heapifyDown(pq, largest);
}
}
void insert(PriorityQueue *pq, int value) {
if (pq->size >= MAX_SIZE) {
printf("Priority Queue is full\n");
return;
}
pq->heap[pq->size] = value;
heapifyUp(pq, pq->size);
pq->size++;
}
int extractMax(PriorityQueue *pq) {
if (pq->size <= 0) {
printf("Priority Queue is empty\n");
return -1;
}
int max = pq->heap[0];
pq->heap[0] = pq->heap[pq->size - 1];
pq->size--;
heapifyDown(pq, 0);
return max;
}
int main() {
PriorityQueue pq;
initPQ(&pq);
insert(&pq, 10);
insert(&pq, 30);
insert(&pq, 20);
insert(&pq, 5);
printf("Highest priority: %d\n", extractMax(&pq));
printf("Next highest priority: %d\n", extractMax(&pq));
return 0;
}
/* 运行结果:
Highest priority: 30
Next highest priority: 20
*/
这个优先队列实现使用了最大堆,确保具有最高优先级(这里用数值大小表示优先级)的元素总是在堆顶,可以在O(log n)时间内进行插入和删除操作。
应用场景
- 任务调度:在操作系统中,优先队列用于实现基于优先级的进程调度。
- Dijkstra最短路径算法:在图算法中,优先队列用于高效地选择下一个要处理的节点。
- 事件驱动模拟:在离散事件模拟中,优先队列用于管理按时间顺序排列的事件。
- 数据压缩:在Huffman编码等数据压缩算法中,优先队列用于构建Huffman树。
- 网络流量管理:在网络路由器中,优先队列用于实现服务质量(QoS)管理。
总结
队列作为一种基础的数据结构,在计算机科学和软件工程中有着广泛的应用。普通队列、双端队列和优先队列各有其特点和适用场景:
- 普通队列适用于需要按照先来先服务原则处理数据的场景。
- 双端队列提供了更灵活的操作,适用于需要在两端进行操作的场景。
- 优先队列在需要根据优先级处理元素的场景中非常有用。
理解这些队列类型及其应用场景,可以帮助开发者选择合适的数据结构来解决特定的问题,从而提高程序的效率和可读性。在实际编程中,根据具体需求选择合适的队列类型,将大大提升算法的性能和代码的质量。