当前位置: 首页 > news >正文

c语言实现栈【由浅入深-数据结构】

文章目录

  • 前言
  • C语言实现栈的详细解析
    • 一、栈的基本概念
      • 栈的核心特性
      • 栈的两个经典操作
    • 二、栈的实现方式
      • 1. 顺序栈(基于数组实现)
        • 结构体定义
        • 代码实现
        • 顺序栈的特点
      • 完整数组实现
      • 2. 链式栈(基于链表实现)
        • 结构体定义
        • 代码实现
        • 链式栈的特点
    • 三、两种实现方式的对比
    • 四、栈的应用场景
    • 五、实际应用示例
      • 括号匹配检查
    • 六、总结


前言

本文介绍c语言实现栈的相关内容。

(【由浅入深】是一个系列文章,它记录了我个人作为一个小白,在学习c++技术开发方向计相关知识过程中的笔记,欢迎各位彭于晏刘亦菲从中指出我的错误并且与我共同学习进步,作为该系列的第一部曲-c语言,大部分知识会根据本人所学和我的助手——通义,DeepSeek等以及合并网络上所找到的相关资料进行核实誊抄,每一篇文章都可能会因为一些错误在后续时间增删改查,因为该系列按照我的网络课程学习笔记形式编写,我会使用绝大多数人使用的讲解顺序编写,所以基础框架和大部分内容案例会与他人一样,基础知识不会过于详细讲述)


C语言实现栈的详细解析

栈(Stack)一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端,称为栈顶,另一端称为栈底。具有后进先出(LIFO, Last In First Out)的特性。它在函数调用、表达式求值、括号匹配等场景有广泛应用。下面我将详细讲解C语言中实现栈的两种主要方式:顺序栈(基于数组-最常用)和链式栈(基于链表)。

一、栈的基本概念

栈的核心特性

  • 后进先出:最后进入的元素最先被取出
  • 操作限制:只能在栈顶进行插入(push)和删除(pop)操作
  • 基本操作时间复杂度:均为O(1)

栈的两个经典操作

  1. 压栈(Push):栈的插入操作,将元素放入栈顶
  2. 出栈(Pop):栈的删除操作,将栈顶元素移除

二、栈的实现方式

1. 顺序栈(基于数组实现)

结构体定义
typedef struct {int* data;      // 存储元素的数组int size;       // 栈的总容量int top;        // 栈顶指针(初始为-1,表示空栈)
} Stack;

关键点

  • top初始值为-1表示空栈
  • size限制栈的最大容量
  • 使用动态数组实现顺序存储
代码实现

初始化栈

Stack* initStack(int n) {Stack* s = (Stack*)malloc(sizeof(Stack));s->data = (int*)malloc(sizeof(int) * n);s->size = n;s->top = -1;  // 初始化栈顶指针return s;
}

入栈操作

int push(Stack* s, int val) {if (s->top == s->size - 1) {// 栈已满,返回错误return -1;}s->top++;s->data[s->top] = val;return 0;  // 成功
}

出栈操作

int pop(Stack* s, int* val) {if (s->top == -1) {// 栈为空,返回错误return -1;}*val = s->data[s->top];s->top--;return 0;  // 成功
}

判空操作

int empty(Stack* s) {return s->top == -1;
}

获取栈顶元素

int top(Stack* s) {if (empty(s)) {return -1;  // 空栈返回错误值}return s->data[s->top];
}
顺序栈的特点
  • 优点:实现简单,内存连续,访问速度快
  • 缺点:容量固定,可能有空间浪费;满栈时需要扩容(增加复杂性)

完整数组实现

#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 STInit(ST* ps)
{// 确保传入的指针有效assert(ps);// 初始化栈成员:// 1. 将动态数组指针置为NULL(表示尚未分配内存)// 2. 栈顶指针初始化为0(表示栈为空,栈顶元素在位置-1,但实际存储从0开始)// 3. 栈容量初始化为0ps->a = NULL;ps->top = 0;ps->capacity = 0;
}// 销毁栈(释放内存并重置状态)
void STDestroy(ST* ps)
{// 确保传入的指针有效assert(ps);// 释放动态数组内存free(ps->a);// 重置栈状态(避免野指针)ps->a = NULL;ps->top = 0;ps->capacity = 0;
}// 入栈操作(压栈)
void STPush(ST* ps, STDataType x)
{// 确保传入的指针有效assert(ps);// 检查栈是否已满(top等于容量表示已无可用空间)if (ps->top == ps->capacity){// 计算新容量:如果当前容量为0(空栈),则分配4个元素空间// 否则,容量翻倍(避免频繁扩容,提高效率)int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;// 重新分配内存,扩展栈容量STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));// 检查内存分配是否成功if (tmp == NULL){perror("realloc fail"); // 打印错误信息return; // 分配失败,退出函数}// 更新栈的数组指针和容量ps->a = tmp;ps->capacity = newcapacity;}// 将元素放入栈顶位置(top指向的位置)ps->a[ps->top] = x;// 栈顶指针后移(指向下一个空位置)ps->top++;
}// 出栈操作(弹栈)
void STPop(ST* ps)
{// 确保传入的指针有效assert(ps);// 检查栈是否为空(不能从空栈弹出元素)assert(!STEmpty(ps));// 栈顶指针前移(相当于移除栈顶元素)ps->top--;
}// 获取栈顶元素
STDataType STTop(ST* ps)
{// 确保传入的指针有效assert(ps);// 检查栈是否为空assert(!STEmpty(ps));// 栈顶元素位于top-1位置(因为top指向下一个空位置)return ps->a[ps->top - 1];
}// 获取栈中元素个数
int STSize(ST* ps)
{// 确保传入的指针有效assert(ps);// 栈中元素个数 = top(因为top表示已使用的元素数量)return ps->top;
}// 判断栈是否为空
bool STEmpty(ST* ps)
{// 确保传入的指针有效assert(ps);// 如果栈顶指针为0,则栈为空return ps->top == 0;
}int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);int top = STTop(&s);printf("%d ", top);STPop(&s);STPush(&s, 4);STPush(&s, 5);while (!STEmpty(&s)){int top = STTop(&s);printf("%d ", top);STPop(&s);}STDestroy(&s);return 0;
}

主要掌握上面数组实现即可


2. 链式栈(基于链表实现)

结构体定义
typedef struct Node {int data;struct Node* next;
} Node;typedef struct {Node* top;  // 栈顶指针int size;   // 栈的大小
} Stack;

关键点

  • 栈顶即为链表的头节点
  • top指向栈顶元素
  • size记录栈中元素个数
代码实现

初始化栈

Stack* initStack() {Stack* s = (Stack*)malloc(sizeof(Stack));s->top = NULL;s->size = 0;return s;
}

入栈操作

void push(Stack* s, int val) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = val;newNode->next = s->top;s->top = newNode;s->size++;
}

出栈操作

int pop(Stack* s, int* val) {if (s->top == NULL) {return -1;  // 栈为空}Node* temp = s->top;*val = temp->data;s->top = temp->next;free(temp);s->size--;return 0;
}

判空操作

int empty(Stack* s) {return s->top == NULL;
}

获取栈顶元素

int top(Stack* s) {if (empty(s)) {return -1;  // 空栈返回错误值}return s->top->data;
}
链式栈的特点
  • 优点:空间利用率高,无需扩容,动态增长
  • 缺点:实现相对复杂,内存不连续,访问速度稍慢

三、两种实现方式的对比

特性顺序栈链式栈
实现基础数组链表
空间利用率低(可能有浪费)高(动态分配)
扩容需要扩容(可能有性能开销)无需扩容
插入/删除效率O(1)O(1)
代码复杂度较低较高
适用场景已知栈大小,需要频繁操作不知道栈大小,需要动态增长

四、栈的应用场景

栈在C语言中有以下广泛应用:

  1. 表达式求值

    • 栈可以用于存储运算符和操作数
    • 实现表达式的求值算法,如中缀表达式转后缀表达式并计算结果
  2. 函数调用

    • 函数调用时,需要保存函数的返回地址、参数和局部变量等信息
    • 这些信息使用栈来保存和管理
  3. 括号匹配

    • 栈可以用于检查括号是否匹配
    • 遇到左括号入栈,遇到右括号出栈,最终检查栈是否为空
  4. 逆波兰表达式求值

    • 逆波兰表达式是一种后缀表达式
    • 栈可以实现逆波兰表达式的求值
  5. 递归算法

    • 递归算法中,每次递归调用时需要保存当前函数的状态
    • 这些状态可以使用栈来保存和管理
  6. 深度优先搜索

    • 栈可以用于实现图的深度优先搜索算法

五、实际应用示例

括号匹配检查

#include <stdio.h>
#include <stdlib.h>typedef struct {char* data;int size;int top;
} Stack;Stack* initStack(int n) {Stack* s = (Stack*)malloc(sizeof(Stack));s->data = (char*)malloc(sizeof(char) * n);s->size = n;s->top = -1;return s;
}int push(Stack* s, char c) {if (s->top == s->size - 1) return -1;s->top++;s->data[s->top] = c;return 0;
}int pop(Stack* s, char* c) {if (s->top == -1) return -1;*c = s->data[s->top];s->top--;return 0;
}int empty(Stack* s) {return s->top == -1;
}int isMatching(char c1, char c2) {return (c1 == '(' && c2 == ')') ||(c1 == '[' && c2 == ']') ||(c1 == '{' && c2 == '}');
}int checkBrackets(char* str) {Stack* s = initStack(100);for (int i = 0; str[i] != '\0'; i++) {if (str[i] == '(' || str[i] == '[' || str[i] == '{') {push(s, str[i]);} else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {char c;if (empty(s) || !isMatching(pop(s, &c), str[i])) {return 0;  // 括号不匹配}}}return empty(s);  // 检查栈是否为空
}int main() {char str1[] = "({[()])}";char str2[] = "({[]})";printf("str1: %s\n", checkBrackets(str1) ? "匹配" : "不匹配");printf("str2: %s\n", checkBrackets(str2) ? "匹配" : "不匹配");return 0;
}

六、总结

  1. 栈的实现

    • 顺序栈:基于数组,实现简单,但容量固定
    • 链式栈:基于链表,空间利用率高,动态增长
  2. 选择建议

    • 如果已知栈的大小,且对性能要求高,选择顺序栈
    • 如果栈的大小不确定,需要动态增长,选择链式栈
  3. 栈的核心价值

    • 通过后进先出的特性,简化了复杂问题的解决
    • 为表达式求值、括号匹配、函数调用等提供了高效解决方案
  4. 重要原则

    • 所有栈操作必须检查栈是否为空(避免空栈出栈错误)
    • 顺序栈需注意容量限制,满栈时需要处理扩容
    • 链式栈需注意内存管理,出栈时需释放节点内存

栈作为基础数据结构,理解其原理和实现方式对学习更复杂的数据结构和算法至关重要。希望这篇详细解析能帮助你深入理解C语言中栈的实现和应用。

http://www.dtcms.com/a/532591.html

相关文章:

  • 教做家常菜的视频网站wordpress 搭建个人博客
  • 【Go】C++ 转 Go 第(五)天:Goroutine 与 Channel | Go 并发编程基础
  • 算法:283. 移动零
  • 设计微信公众号的网站吗举例说明seo
  • 欧米伽男士手表官方网站wordpress下载类型主题
  • Chrome离线版下载版,Chrome离线版安装文件,Chrome离线包
  • 上饶网站建设多少钱分销网站有哪些
  • 阿里云 Qwen 模型的 OpenAI SDK 调用
  • 什么是提示词(Prompt),提示词类型、结构解析
  • MES系列-制造流程数字化的实现
  • 我想在网站上卖食品怎么做百度知道网址
  • 对于使用队列实现栈以及用栈实现队列的题目的解析
  • Spring Boot3零基础教程,事件驱动开发,设计登录成功后增加积分记录信息功能,笔记61
  • 网站开发进度表网络电话免费版
  • 两种Redis序列化对比
  • 精确优化长尾关键词以提升SEO效果的战略分析
  • 分析对手网站wordpress制作功能型网站
  • Spring AOP注解配置实战:从概念到代码实现的深度解析(含核心关键词)
  • 【图像算法 - 31】基于深度学习的太阳能板缺陷检测系统:YOLOv12 + UI界面 + 数据集实现
  • 火山方舟 Responses API 实战指南:从概念到「公司尽调 Dossier 生成器」
  • 【推荐系统3】向量召回:i2i召回、u2i召回
  • 网站建设及系统开发wordpress仿微信菜单栏
  • 网站死链接怎么处理网页版浏览器怎么设置
  • 【仿RabbitMQ的发布订阅式消息队列】--- 介绍
  • Frobenius范数:矩阵分析的万能度量尺
  • 做网站 php asp.net jsp学院网站建设实例
  • [论文阅读] 从 5MB 到 1.6GB 数据:Java/Scala/Python 在 Spark 中的性能表现全解析
  • 算法--滑动窗口(一)
  • 新房网站建设公司永和建设集团有限公司网站
  • 【Rust编程:从新手到大师】Rust 环境搭建(详细版)