实用指南:栈和队列的实现详解

栈(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

设计模式总结

栈的设计模式:

  1. 数组实现:支持随机访问,缓存友好

  2. 动态扩容:自动处理容量不足

  3. top指针设计:指向下一个位置,便于计算大小

  4. 错误处理:使用assert确保操作合法性

队列的设计模式:

  1. 链表实现:避免出队时的数据移动

  2. 头尾指针:支持O(1)时间的入队和出队

  3. size记录:避免遍历计数

  4. 边界处理:特别注意空队列和单节点队列的情况

实际应用场景

栈的应用:

  • 函数调用栈

  • 表达式求值

  • 括号匹配检查

  • 浏览器前进后退

  • 撤销操作功能

队列的应用:

  • 消息队列系统

  • 广度优先搜索

  • 线程池任务调度

  • 打印机任务队列

  • 网络数据包缓冲

代码质量要点

  1. 内存安全:所有动态分配的内存都有对应的释放

  2. 错误处理:使用assert进行参数验证

  3. 边界条件:正确处理空数据结构的情况

  4. 接口清晰:函数命名和参数设计合理

  5. 性能考虑:主要操作都达到最优时间复杂度

这个完整分析涵盖了栈和队列的所有函数实现细节、设计原理和实际应用,希望能够满足您的需求。

posted @ 2025-12-20 09:27  clnchanpin  阅读(52)  评论(0)    收藏  举报