实用指南:栈和队列的实现详解
栈(Stack)的完整函数分析
栈的结构和初始化
c
typedef struct Stack
{
STDataType* a; // 动态数组存储栈元素
int top; // 栈顶指针
int capacity; // 栈容量
} ST;
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0; // top指向栈顶数据的下一个位置
ps->capacity = 0;
}
设计要点:
使用动态数组实现,便于自动扩容
top指针指向下一个可插入位置,这种设计便于计算元素个数
栈的完整操作函数
1. 销毁栈
c
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a); // 释放动态数组
ps->a = NULL; // 防止野指针
ps->top = ps->capacity = 0;
}
2. 入栈操作(带自动扩容)
c
void STPush(ST* ps, STDataType x)
{
assert(ps);
// 检查容量并扩容
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* temp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
if (temp == NULL)
{
perror("realloc fail!");
return;
}
ps->a = temp;
ps->capacity = newcapacity;
}
// 插入元素并更新栈顶
ps->a[ps->top++] = x;
}
扩容策略:
初始容量:4个元素
扩容倍数:2倍增长
使用
realloc进行动态扩容
3. 出栈操作
c
void STPop(ST* ps)
{
assert(ps);
assert(ps->top > 0); // 栈不能为空
ps->top--; // 只需移动指针,逻辑删除
}
4. 获取栈顶元素
c
STDataType STTop(ST* ps)
{
assert(ps);
assert(ps->top > 0); // 栈不能为空
return ps->a[ps->top - 1]; // 返回栈顶元素
}
5. 获取栈大小
c
int STSize(ST* ps)
{
assert(ps);
return ps->top; // top就是元素个数
}
6. 判断栈是否为空
c
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0; // 简洁的判断方式
}
7. 打印栈元素
c
void STPrint(ST* ps)
{
for (int i = 0; i < ps->top; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
队列(Queue)的完整函数分析
队列的结构定义
c
// 队列节点
typedef struct QListNode
{
struct QListNode* next;
QDataType val;
} QNode;
// 队列结构
typedef struct Queue
{
QNode* phead; // 队头指针
QNode* ptail; // 队尾指针
int size; // 队列大小
} Queue;
设计要点:
使用单向链表实现
维护头尾指针,便于两端操作
使用
size记录元素个数,避免遍历计数
队列的完整操作函数
1. 初始化队列
c
void QueueInit(Queue* q)
{
assert(q);
q->phead = NULL;
q->ptail = NULL;
q->size = 0;
}
2. 入队操作
c
void QueuePush(Queue* q, QDataType x)
{
assert(q);
QNode* node = (QNode*)malloc(sizeof(QNode));
if (node == NULL)
{
perror("malloc fail");
return;
}
node->next = NULL;
node->val = x;
if (q->phead == NULL) // 队列为空
{
q->phead = node;
q->ptail = node;
}
else // 队列不为空
{
q->ptail->next = node;
q->ptail = node;
}
q->size++;
}
3. 出队操作(重点)
c
void QueuePop(Queue* q)
{
assert(q);
assert(q->size != 0);
// 分两种情况处理
if (q->phead->next == NULL) // 只有一个节点
{
free(q->phead);
q->phead = NULL;
q->ptail = NULL;
}
else // 多个节点
{
QNode* temp = q->phead->next;
free(q->phead);
q->phead = temp;
}
q->size--;
}
关键点:
需要处理只有一个节点的特殊情况
释放内存后正确更新头指针
只有一个节点时,头尾指针都需要置空
4. 获取队头元素
c
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->phead); // 队列不能为空
return q->phead->val;
}
5. 获取队尾元素
c
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->ptail); // 队列不能为空
return q->ptail->val;
}
6. 获取队列大小
c
int QueueSize(Queue* q)
{
assert(q);
return q->size; // 直接返回记录的大小
}
7. 判断队列是否为空
c
bool QueueEmpty(Queue* q)
{
assert(q);
return q->size == 0; // 使用size判断
}
8. 销毁队列
c
void QueueDestry(Queue* q)
{
assert(q);
QNode* cur = q->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->phead = NULL;
q->ptail = NULL;
q->size = 0;
}
销毁要点:
需要遍历整个链表释放所有节点
释放后头尾指针都要置空
size重置为0
完整的时间复杂度分析
栈操作时间复杂度:
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 初始化 | O(1) | 常量时间 |
| 销毁 | O(1) | 释放数组 |
| 入栈 | 平均O(1) | 分摊到每次操作的扩容成本 |
| 出栈 | O(1) | 直接移动指针 |
| 取栈顶 | O(1) | 数组随机访问 |
| 获取大小 | O(1) | 直接返回top |
| 判断空 | O(1) | 判断top是否为0 |
队列操作时间复杂度:
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 初始化 | O(1) | 常量时间 |
| 销毁 | O(n) | 需要遍历链表释放节点 |
| 入队 | O(1) | 链表尾部插入 |
| 出队 | O(1) | 链表头部删除 |
| 取队头 | O(1) | 直接访问头节点 |
| 取队尾 | O(1) | 直接访问尾节点 |
| 获取大小 | O(1) | 直接返回size |
| 判断空 | O(1) | 判断size是否为0 |
设计模式总结
栈的设计模式:
数组实现:支持随机访问,缓存友好
动态扩容:自动处理容量不足
top指针设计:指向下一个位置,便于计算大小
错误处理:使用assert确保操作合法性
队列的设计模式:
链表实现:避免出队时的数据移动
头尾指针:支持O(1)时间的入队和出队
size记录:避免遍历计数
边界处理:特别注意空队列和单节点队列的情况
实际应用场景
栈的应用:
函数调用栈
表达式求值
括号匹配检查
浏览器前进后退
撤销操作功能
队列的应用:
消息队列系统
广度优先搜索
线程池任务调度
打印机任务队列
网络数据包缓冲
代码质量要点
内存安全:所有动态分配的内存都有对应的释放
错误处理:使用assert进行参数验证
边界条件:正确处理空数据结构的情况
接口清晰:函数命名和参数设计合理
性能考虑:主要操作都达到最优时间复杂度
这个完整分析涵盖了栈和队列的所有函数实现细节、设计原理和实际应用,希望能够满足您的需求。
浙公网安备 33010602011771号