队列是一种先进先出(FIFO,First In First Out)的数据结构。想象一下你在银行排队等待办理业务,先到的人先办理,后到的人依次等待,这就是队列的典型应用场景。在计算机科学中,队列广泛应用于任务调度、事件处理等场景,比如操作系统中的进程调度队列、打印机任务队列等。
C语言以其高效性和对硬件的直接操作能力而闻名。虽然它没有像Python、Java等高级语言那样内置丰富的数据结构库,但正是这种“原始性”让我们有机会从底层构建数据结构,深入理解其原理。通过C语言实现队列,不仅能锻炼我们的编程能力,还能让我们更好地理解内存分配、指针操作等核心概念。
我们首先定义了队列的基本结构。队列由一个链表实现,包含头指针(head)、尾指针(tail)和队列大小(size)。链表的每个节点(QNode)存储一个字符类型的数据(QDatatype)和指向下一个节点的指针(next)。
typedef char QDatatype;
typedef struct QueueNode
{
struct QueueNode* next;
QDatatype data;
} QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
} Queue;
在使用队列之前,我们需要对其进行初始化,将头指针和尾指针置为NULL,队列大小置为0。销毁队列时,我们需要逐个释放链表节点的内存,避免内存泄漏。
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
入队操作(QueuePush)是将一个新节点添加到队列尾部。如果队列为空,新节点既是头节点也是尾节点;否则,将新节点连接到当前尾节点的后面,并更新尾指针。出队操作(QueuePop)则是移除队列头部的节点,并更新头指针。如果队列只剩下一个节点,出队后需要将尾指针也置为NULL。
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* NewNode = malloc(sizeof(QNode));
NewNode->next = NULL;
NewNode->data = x;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = NewNode;
}
else
{
pq->tail->next = NewNode;
pq->tail = NewNode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head);
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
pq->tail = NULL;
pq->size--;
}
我们还提供了获取队列大小(QueueSize)、判断队列是否为空(QueueEmpty)以及获取队列头部和尾部数据(QueueFront、QueueBack)的操作。这些操作都非常简单,只需要根据队列的结构进行相应的指针访问即可。
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->tail->data;
}
在test1函数中,我们创建了一个队列,依次将字符’z’、‘h’、‘a’、‘n’、'g’入队,然后逐个出队并打印队列头部的数据。最终输出结果为zhang,完美展示了队列的先进先出特性。
void test1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 'z');
QueuePush(&q, 'h');
QueuePush(&q, 'a');
QueuePush(&q, 'n');
QueuePush(&q, 'g');
while (q.size)
{
printf("%c", QueueFront(&q));
QueuePop(&q);
}
QueueDestroy(&q);
}
链接: 栈的介绍
栈和队列都是线性数据结构,但栈是后进先出(LIFO),主要用于函数调用、表达式求值等;队列是先进先出(FIFO),常用于任务调度和事件处理。栈通常用数组或链表实现,操作集中在一端;队列用链表或循环数组实现,操作在两端。两者可相互转换,例如用两个栈实现队列,或用两个队列实现栈。在实际应用中,栈适合嵌套和回溯问题,队列适合顺序处理和调度任务。
通过这段代码,我们不仅实现了一个功能完整的队列,还深入理解了队列的底层实现原理。在实际开发中,掌握这种从零构建数据结构的能力是非常重要的。它不仅能帮助我们解决实际问题,还能让我们在面对复杂问题时,能够从底层逻辑出发,找到最优的解决方案。
队列只是数据结构世界中的一个小小角落,但它却有着无限的可能。希望这篇文章能激发你对数据结构的兴趣,让你在编程的道路上越走越远。