栈(Stack)和队列(Queue)是计算机科学中最基础且重要的数据结构。它们分别遵循**后进先出(LIFO)和先进先出(FIFO)**的原则,广泛应用于算法设计、系统底层实现(如函数调用栈)、任务调度等场景。本文将从概念、实现到常见面试题,带你全面掌握这两种数据结构。
动态栈通过动态数组实现,包含以下核心结构:
#define INICAPA 4 // 初始容量
typedef struct Stack {
STDataType* a; // 动态数组
int top; // 栈顶指针(指向下一个待插入位置)
int capacity; // 当前容量
} ST;
a
:动态数组,存储栈元素。top
:栈顶指针,表示栈中元素个数,也指向下一个插入位置。capacity
:数组当前容量,支持动态扩容。void STInit(ST* stack) {
assert(stack);
stack->a = (STDataType*)malloc(sizeof(STDataType) * INICAPA);
if (stack->a == NULL) {
perror("malloc fail");
exit(-1);
}
stack->capacity = INICAPA;
stack->top = 0; // 初始时栈空
}
assert
确保传入指针非空。void STPush(ST* stack, STDataType x) {
assert(stack);
// 栈满时扩容至2倍
if (stack->top == stack->capacity) {
STDataType* tmp = (STDataType*)realloc(
stack->a,
sizeof(STDataType) * stack->capacity * 2
);
if (tmp == NULL) {
perror("realloc fail");
exit(-1);
}
stack->a = tmp;
stack->capacity *= 2;
}
// 插入数据并更新栈顶
stack->a[stack->top] = x;
stack->top++;
}
top == capacity
)。realloc
扩容至原容量的2倍。top
位置,并递增栈顶指针。void STPop(ST* stack) {
assert(stack);
assert(!STEmpty(stack)); // 栈不能为空
stack->top--;
}
top
减1,无需实际删除数据。STDataType STTop(ST* stack) {
assert(stack && !STEmpty(stack));
return stack->a[stack->top - 1];
}
top-1
访问当前栈顶元素。void STDestroy(ST* stack) {
assert(stack);
free(stack->a); // 释放动态数组
stack->a = NULL; // 避免野指针
stack->top = 0;
stack->capacity = 0;
}
a
置为 NULL
可能导致后续误用。// 判空
bool STEmpty(ST* stack) {
assert(stack);
return stack->top == 0;
}
// 获取栈大小
int STSize(ST* stack) {
assert(stack);
return stack->top;
}
top
指针的设计top=3
表示栈中有3个元素(索引0~2),新元素插入索引3。top
直接表示元素个数,无需额外维护大小字段。STDestroy
,否则导致内存泄漏。队列通过链表实现,包含以下核心结构:
// 队列节点
typedef struct QListNode {
struct QListNode* next; // 指向下一节点
QDataType data; // 节点数据
} QNode;
// 队列管理结构
typedef struct Queue {
QNode* head; // 队头指针
QNode* tail; // 队尾指针
int size; // 队列元素个数
} Queue;
head
:指向链表的第一个节点(队头),用于出队操作。tail
:指向链表的最后一个节点(队尾),用于入队操作。size
:记录队列长度,避免遍历链表统计元素。void QueueInit(Queue* q) {
assert(q);
q->head = q->tail = NULL;
q->size = 0;
}
size
设为0。assert
确保传入的队列指针非空。void QueuePush(Queue* q, QDataType data) {
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL) {
perror("malloc fail");
exit(-1);
}
newnode->next = NULL;
newnode->data = data;
if (q->tail == NULL) { // 队列为空
q->tail = q->head = newnode;
} else { // 队列非空
q->tail->next = newnode;
q->tail = newnode;
}
q->size++;
}
void QueuePop(Queue* q) {
assert(q);
assert(!QueueEmpty(q));
if (q->head->next == NULL) { // 队列仅剩一个节点
free(q->head);
q->head = q->tail = NULL;
} else { // 队列有多个节点
QNode* next = q->head->next;
free(q->head);
q->head = next;
}
q->size--;
}
QueueEmpty
)。size
。QDataType QueueFront(Queue* q) {
assert(q && !QueueEmpty(q));
return q->head->data;
}
QDataType QueueBack(Queue* q) {
assert(q && !QueueEmpty(q));
return q->tail->data;
}
void QueueDestroy(Queue* q) {
assert(q);
QNode* cur = q->head;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
q->tail = q->head = NULL;
q->size = 0;
}
size
。size
字段的作用q->size
获取队列长度,避免遍历链表((O(1)) 时间复杂度)。size == 0
,提升效率。栈和队列是算法设计的基石,理解其原理和实现是程序员的基本功。建议通过实际编码练习(如实现动态栈、循环队列)加深理解,并多刷相关面试题提升应用能力。