【C语言】栈(Stack)数据结构的实现与应用

引言

栈是一种重要的线性数据结构,它遵循**后进先出(LIFO)**的原则。在计算机科学中,栈有着广泛的应用,如函数调用、表达式求值、括号匹配等。本文将详细分析栈的C语言实现,并通过实际测试和应用案例来展示其强大功能。

目录

引言

栈的数据结构定义

核心设计要点

栈的实现细节分析

[1. 初始化与销毁](#1. 初始化与销毁)

[2. 动态扩容机制](#2. 动态扩容机制)

[3. 栈操作的安全性](#3. 栈操作的安全性)

功能测试验证

实际应用:括号匹配算法

总结


栈的数据结构定义

首先,让我们从栈的头文件开始分析:

复制代码
// stack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int STDataType;
typedef struct Stack
{
    STDataType* a;    // 动态数组存储栈元素
    int top;          // 栈顶指针
    int capacity;     // 栈容量
} ST;

// 初始化栈
void STInit(ST* ps);
// 销毁栈  
void STDestroy(ST* ps);
// 入栈
void STPush(ST* ps, STDataType x);
// 出栈
void STPop(ST* ps);
// 取出栈顶元素
STDataType STTop(ST* ps);
// 获取栈中有效元素个数
int STSize(ST* ps);
// 栈是否为空
bool STEmpty(ST* ps);
// 打印栈中的元素
void STPrint(ST* ps);

核心设计要点

  1. 动态数组存储:使用动态分配的数组来存储元素,支持自动扩容

  2. 泛型设计 :通过typedef int STDataType可以轻松改变存储的数据类型

  3. top指针设计top指向栈顶的下一个位置,这种设计便于计算栈大小

栈的实现细节分析

1. 初始化与销毁

复制代码
// 初始化栈
void STInit(ST* ps)
{
    assert(ps);
    ps->a = NULL;
    ps->top = 0;        // 指向下一个可用位置
    ps->capacity = 0;
}

// 销毁栈
void STDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);        // 释放动态数组
    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

关键点

  • 初始化时将数组指针设为NULL,采用懒加载策略

  • 销毁时确保释放所有动态分配的内存,避免内存泄漏

2. 动态扩容机制

复制代码
// 入栈
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;  // 存入元素并移动top指针
}

扩容策略分析

  • 初始容量:0,第一次扩容到4

  • 后续扩容:每次容量翻倍

  • 时间复杂度:均摊O(1),虽然单次扩容是O(n),但均摊到每个操作上仍是常数时间

3. 栈操作的安全性

复制代码
// 出栈
void STPop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);  // 确保栈不为空
    ps->top--;
}

// 取出栈顶元素
STDataType STTop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);  // 确保栈不为空
    return ps->a[ps->top - 1];
}

安全机制

  • 使用assert确保在空栈上不会执行非法操作

  • 通过ps->top > 0检查避免访问无效内存

功能测试验证

让我们编写测试代码来验证栈的实现:

复制代码
// test.c
#include "stack.h"
#include <stdio.h>

void TestStackBasic() {
    printf("=== 栈基础功能测试 ===\n");
    ST stack;
    STInit(&stack);
    
    // 测试入栈
    for(int i = 1; i <= 10; i++) {
        STPush(&stack, i * 10);
        printf("入栈: %d, 栈大小: %d, 容量: %d\n", 
               i * 10, STSize(&stack), stack.capacity);
    }
    
    // 测试栈顶和出栈
    while(!STEmpty(&stack)) {
        printf("栈顶元素: %d, 出栈后", STTop(&stack));
        STPop(&stack);
        printf("栈大小: %d\n", STSize(&stack));
    }
    
    STDestroy(&stack);
    printf("基础测试完成!\n\n");
}

void TestStackEdgeCases() {
    printf("=== 边界情况测试 ===\n");
    ST stack;
    STInit(&stack);
    
    // 测试空栈操作
    printf("空栈大小: %d\n", STSize(&stack));
    printf("栈是否为空: %s\n", STEmpty(&stack) ? "是" : "否");
    
    // 单元素测试
    STPush(&stack, 100);
    printf("单元素栈顶: %d\n", STTop(&stack));
    STPop(&stack);
    printf("弹出后是否为空: %s\n", STEmpty(&stack) ? "是" : "否");
    
    STDestroy(&stack);
    printf("边界测试完成!\n\n");
}

int main() {
    TestStackBasic();
    TestStackEdgeCases();
    return 0;
}

测试结果分析

通过测试我们可以验证:

  1. 动态扩容机制正常工作

  2. 栈的LIFO特性得到保证

  3. 边界情况处理正确

  4. 内存管理没有泄漏

实际应用:括号匹配算法

有了可靠的栈实现,我们可以用它来解决实际问题。括号匹配是栈的经典应用场景:

复制代码
bool isValid(char* s) {
    ST st;
    STInit(&st);
    
    while(*s) {
        // 左括号入栈
        if(*s == '(' || *s == '[' || *s == '{') {
            STPush(&st, *s);
        } 
        // 右括号检查匹配
        else {
            if(STEmpty(&st)) {
                STDestroy(&st);
                return false;  // 右括号多余
            }
            
            char topChar = STTop(&st);
            STPop(&st);
            
            // 检查括号类型是否匹配
            if((*s == ')' && topChar != '(') ||
               (*s == ']' && topChar != '[') ||
               (*s == '}' && topChar != '{')) {
                STDestroy(&st);
                return false;  // 类型不匹配
            }
        }
        s++;
    }
    
    bool result = STEmpty(&st);
    STDestroy(&st);
    return result;  // 栈为空说明全部匹配
}

算法优势

  • 时间复杂度:O(n),每个字符处理一次

  • 空间复杂度:O(n),最坏情况存储所有左括号

  • 利用栈的LIFO特性完美解决"最近优先"的匹配需求

总结

通过完整的栈实现和测试,我们展示了:

  1. 健壮的数据结构设计:动态扩容、安全检查、完整的内存管理

  2. 清晰的接口设计:每个函数职责单一,易于理解和使用

  3. 实用的测试验证:通过全面测试确保代码质量

  4. 经典的应用场景:括号匹配算法体现了栈的实际价值

这种从底层实现到上层应用的完整分析,不仅帮助我们深入理解数据结构,也展示了如何编写高质量、可维护的C语言代码。栈作为基础数据结构,其设计思想和实现技巧可以推广到其他更复杂的数据结构中。

相关推荐
闻缺陷则喜何志丹2 小时前
【计算几何 SAT轴】P6732 「Wdsr-2」方分|普及+
c++·数学·计算几何·sat轴·凸多边形分离
embrace992 小时前
【C语言学习】预处理详解
java·c语言·开发语言·数据结构·c++·学习·算法
拼好饭和她皆失2 小时前
《二分答案算法精讲:从原理到实战(上篇)》
c++·算法
czlczl200209252 小时前
Spring Boot + Redis :如何设计“登出”功能
spring boot·redis·后端
helloworddm2 小时前
C++与C#交互 回调封装为await
c++·c#·交互
应用市场2 小时前
TCP网络连接断开检测机制详解——C++实现网络连通性判断与断线类型识别
网络·c++·tcp/ip
浅尝辄止;2 小时前
C# 优雅实现 HttpClient 封装(可直接复用的工具类)
开发语言·c#
林太白2 小时前
Rust01-认识安装
开发语言·后端·rust
重生之我是Java开发战士2 小时前
【数据结构】优先级队列(堆)
java·数据结构·算法