数据结构:栈(Stack)及其实现

栈(Stack)是计算机科学中常用的一种数据结构,它遵循先进后出(Last In, First Out,LIFO)的原则。也就是说,栈中的元素只能从栈顶进行访问,最后放入栈中的元素最先被取出。栈在很多应用中非常重要,例如函数调用、表达式求值、深度优先搜索等。

本文将介绍栈的基本概念、实现逻辑,并通过C语言代码演示栈的常见操作。


1. 栈的基本概念

栈是一种线性数据结构,其主要操作包括:

  • 压栈(Push):将一个元素压入栈顶。
  • 弹栈(Pop):从栈顶移除一个元素。
  • 栈顶元素(Top/Peek):获取栈顶的元素,但不移除它。
  • 栈空(IsEmpty):检查栈是否为空。
  • 栈满(IsFull):检查栈是否已满(对于固定大小的栈)。

栈的特点是只能从栈顶进行操作,所有元素的插入和删除都发生在栈顶。


2. 栈的实现逻辑

栈的实现可以基于数组或链表。这里我们采用数组实现栈,并且实现栈的常见操作。

  1. 栈的初始化:栈需要一个数组来存储元素,并且需要一个指针top来指示栈顶的位置。
  2. 压栈操作(Push):将元素放入栈顶,top指针向上移动。
  3. 弹栈操作(Pop):移除栈顶元素,top指针向下移动。
  4. 查看栈顶元素(Top/Peek):返回top指针位置的元素。
  5. 检查栈空(IsEmpty):判断top是否为-1,栈为空时top = -1
  6. 检查栈满(IsFull):判断top是否等于栈的最大容量减1。

3. C语言实现栈

#include <stdio.h>
#include <stdlib.h>#define MAX 5  // 栈的最大容量// 栈结构体
typedef struct {int arr[MAX];  // 存储栈元素的数组int top;       // 栈顶指针
} Stack;// 栈初始化函数
void initStack(Stack* stack) {stack->top = -1;  // 初始时栈为空,栈顶指针为-1
}// 判断栈是否为空
int isEmpty(Stack* stack) {return stack->top == -1;  // 如果栈顶指针为-1,说明栈为空
}// 判断栈是否为满
int isFull(Stack* stack) {return stack->top == MAX - 1;  // 如果栈顶指针为MAX-1,说明栈已满
}// 压栈操作
void push(Stack* stack, int value) {if (isFull(stack)) {printf("栈满,无法压栈!\n");return;}stack->arr[++(stack->top)] = value;  // 将元素放入栈顶,并更新栈顶指针printf("元素 %d 压栈成功!\n", value);
}// 弹栈操作
int pop(Stack* stack) {if (isEmpty(stack)) {printf("栈空,无法弹栈!\n");return -1;}return stack->arr[(stack->top)--];  // 返回栈顶元素,并更新栈顶指针
}// 获取栈顶元素
int peek(Stack* stack) {if (isEmpty(stack)) {printf("栈空,无法获取栈顶元素!\n");return -1;}return stack->arr[stack->top];  // 返回栈顶元素
}// 打印栈的所有元素
void printStack(Stack* stack) {if (isEmpty(stack)) {printf("栈空,无法打印栈元素!\n");return;}printf("栈内元素:");for (int i = 0; i <= stack->top; i++) {printf("%d ", stack->arr[i]);}printf("\n");
}int main() {Stack stack;initStack(&stack);  // 初始化栈// 压栈操作push(&stack, 10);push(&stack, 20);push(&stack, 30);push(&stack, 40);push(&stack, 50);// 打印栈元素printStack(&stack);// 再压栈时,栈满的情况push(&stack, 60);// 弹栈操作printf("弹栈元素: %d\n", pop(&stack));printf("弹栈元素: %d\n", pop(&stack));// 打印栈元素printStack(&stack);// 查看栈顶元素printf("栈顶元素: %d\n", peek(&stack));return 0;
}

4. 代码注释说明

  • 栈的结构体Stack结构体包含了一个大小为MAX的数组arr来存储栈元素,以及一个top变量来记录栈顶的位置。top的初始值为-1,表示栈为空。

  • initStack函数:初始化栈,将top设为-1,表示栈为空。

  • isEmpty函数:判断栈是否为空。如果top-1,返回1表示栈空;否则返回0

  • isFull函数:判断栈是否为满。如果top等于MAX-1,说明栈已满,返回1表示栈满;否则返回0

  • push函数:将元素压入栈顶,首先检查栈是否已满,如果未满,top指针加1,然后将元素放入栈顶。

  • pop函数:从栈顶移除元素,首先检查栈是否为空,如果栈不为空,返回栈顶元素并将top指针减1。

  • peek函数:查看栈顶元素,但不移除它。首先检查栈是否为空,如果不为空,返回栈顶元素。

  • printStack函数:打印栈中所有的元素。首先检查栈是否为空,如果栈不为空,则依次打印从栈底到栈顶的所有元素。

  • 5. 运行示例

    假设我们运行上述程序,输出结果如下:

元素 10 压栈成功!
元素 20 压栈成功!
元素 30 压栈成功!
元素 40 压栈成功!
元素 50 压栈成功!
栈内元素:10 20 30 40 50 
栈满,无法压栈!
弹栈元素: 50
弹栈元素: 40
栈内元素:10 20 30 
栈顶元素: 30
  • 初始时,栈为空,通过压栈操作逐步将元素10, 20, 30, 40, 50压入栈中。
  • 然后尝试压栈一个新的元素60,由于栈已满,无法压栈。
  • 接着,弹出栈顶元素5040,并打印当前栈中的元素。
  • 最后,查看栈顶元素30

6.栈在实际应用中的一些常见使用场景

6.1. 函数调用和递归

栈最常见的应用之一是函数调用,它在计算机的程序执行过程中被广泛使用。

实现过程

当程序调用一个函数时,程序会将当前函数的执行状态(如局部变量、返回地址等)保存在栈中,这个过程叫做“入栈”。

  • 当一个函数调用另一个函数时,栈会继续“压入”当前函数的状态。
  • 当一个函数执行完毕,栈会“弹出”当前函数的状态,并恢复到上一个函数的执行状态。

这种机制是通过栈来实现的,也就是函数调用栈。当递归调用发生时,每次递归调用都将当前的执行环境推入栈中,直到递归结束并返回时,栈中的元素逐步弹出,恢复到原来的状态。

示例

递归计算斐波那契数列时,栈用于存储每次递归调用的状态。

int fibonacci(int n) {if (n <= 1) {return n;}return fibonacci(n-1) + fibonacci(n-2);
}

每次fibonacci函数调用时,程序都会把当前调用的返回地址和局部变量压入栈中。栈的大小随着递归深度的增加而增加,直到递归返回时,栈逐一弹出,恢复状态。

6.2. 表达式求值

栈是计算机中表达式求值的常用工具,特别是在处理后缀表达式(逆波兰表示法)和前缀表达式时。

后缀表达式(逆波兰表示法)

例如,给定一个表达式3 4 + 5 *,可以通过栈来实现求值。

  • 遇到操作数(如3、4),将其压入栈。
  • 遇到操作符(如+、*),弹出栈顶的两个操作数,进行计算,然后将结果重新压入栈。
  • 最终栈中剩下的就是结果。

示例

例如计算表达式3 4 + 5 *

  • 第一步:遇到34,压栈,栈为[3, 4]
  • 第二步:遇到+,弹出栈顶的两个数43,计算3 + 4 = 7,将结果压入栈,栈为[7]
  • 第三步:遇到5,压栈,栈为[7, 5]
  • 第四步:遇到*,弹出栈顶的两个数57,计算7 * 5 = 35,将结果压入栈,栈为[35]

最终栈中的元素即为结果35

6.3. 括号匹配

栈常用于括号匹配问题,特别是在编译器和解释器中,栈是检查是否存在匹配括号的一种有效方式。

实现过程

通过栈可以有效地检查括号的匹配情况,例如:

  • 遇到一个左括号(时,将其压入栈中。
  • 遇到右括号)时,弹出栈顶元素,并确保栈中存在对应的左括号(
  • 如果栈为空或者遇到不匹配的括号,则说明括号不匹配。

示例

检查括号匹配的代码:

#include <stdio.h>
#include <stdbool.h>#define MAX 100typedef struct {char items[MAX];int top;
} Stack;void initStack(Stack* stack) {stack->top = -1;
}bool isEmpty(Stack* stack) {return stack->top == -1;
}void push(Stack* stack, char value) {stack->items[++(stack->top)] = value;
}char pop(Stack* stack) {return stack->items[(stack->top)--];
}bool isValid(char* s) {Stack stack;initStack(&stack);for (int i = 0; s[i] != '\0'; i++) {if (s[i] == '(') {push(&stack, s[i]);} else if (s[i] == ')') {if (isEmpty(&stack)) {return false; // 没有匹配的左括号}pop(&stack); // 弹出栈顶左括号}}return isEmpty(&stack); // 如果栈为空,说明所有括号匹配
}int main() {char s[] = "(())";if (isValid(s)) {printf("括号匹配!\n");} else {printf("括号不匹配!\n");}return 0;
}
6.4. 浏览器历史记录和撤销操作

栈还被广泛用于实现浏览器的历史记录撤销操作,这是由于栈的后进先出(LIFO)特性。

浏览器历史记录

浏览器通过栈来保存用户访问的网页,每访问一个新网页时,当前网页的URL会被压入栈中,用户点击"后退"按钮时,浏览器会从栈顶弹出上一个网页的URL,展示该页面。

撤销操作

例如在文本编辑器中,栈用于保存操作记录(如文本插入、删除等),当用户点击"撤销"时,可以通过弹出栈顶的操作记录来撤销最近的操作。

6.5. 深度优先搜索(DFS)

栈是深度优先搜索(DFS)算法的基础。在图的遍历中,深度优先搜索使用栈来存储待访问的节点,直到访问完所有可能的节点。

实现过程

  • 从某个节点开始,访问该节点并将其压入栈中。
  • 然后遍历该节点的所有未访问的邻居,将它们压入栈中。
  • 每次弹出栈顶节点,继续遍历该节点的邻居,直到栈为空为止。

这种方式常用于遍历无向图、有向图或解决迷宫问题等。

6.6. 逆序操作

栈可以用于实现逆序操作,例如将一个序列反转。

实现过程:

通过将序列中的元素压入栈中,然后再从栈中逐个弹出元素,这样就能得到一个逆序的序列。

示例

反转字符串的代码:

#include <stdio.h>
#include <string.h>#define MAX 100typedef struct {char items[MAX];int top;
} Stack;void initStack(Stack* stack) {stack->top = -1;
}void push(Stack* stack, char value) {stack->items[++(stack->top)] = value;
}char pop(Stack* stack) {return stack->items[(stack->top)--];
}void reverseString(char* str) {Stack stack;initStack(&stack);int length = strlen(str);// 将字符串字符压入栈for (int i = 0; i < length; i++) {push(&stack, str[i]);}// 从栈中弹出字符,形成逆序字符串for (int i = 0; i < length; i++) {str[i] = pop(&stack);}
}int main() {char str[] = "Hello, World!";reverseString(str);printf("逆序字符串: %s\n", str);return 0;
}

7. 总结

  • 栈是一种遵循LIFO(先进后出)原则的线性数据结构,它的常见应用包括递归函数调用、表达式求值、括号匹配等。本文通过C语言实现了一个基于数组的栈,演示了栈的基本操作:压栈、弹栈、查看栈顶元素、判断栈空和栈满,并附上了详细的注释。栈是一种非常简单但功能强大的数据结构,理解栈的基本操作和应用场景对于学习计算机科学非常重要。

  • 栈作为一种数据结构,在许多计算机科学和实际应用中都扮演着重要的角色。其最典型的应用包括:

  • 函数调用和递归

  • 表达式求值

  • 括号匹配

  • 浏览器历史记录撤销操作

  • 深度优先搜索

  • 逆序操作

版权声明:本文为原创文章,转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/70128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pdf-extract-kit paddle paddleocr pdf2markdown.py(效果不佳)

GitHub - opendatalab/PDF-Extract-Kit: A Comprehensive Toolkit for High-Quality PDF Content Extraction https://github.com/opendatalab/PDF-Extract-Kit pdf2markdown.py 运行遇到的问题&#xff1a; 错误&#xff1a; -------------------------------------- C Tra…

深度学习之图像回归(一)

前言 图像回归任务主要是理解一个最简单的深度学习相关项目的结构&#xff0c;整体的思路&#xff0c;数据集的处理&#xff0c;模型的训练过程和优化处理。 因为深度学习的项目思路是差不多的&#xff0c;主要的区别是对于数据集的处理阶段&#xff0c;之后模型训练有一些小…

DFS算法篇:理解递归,熟悉递归,成为递归

1.DFS原理 那么dfs就是大家熟知的一个深度优先搜索&#xff0c;那么听起来很高大尚的一个名字&#xff0c;但是实际上dfs的本质就是一个递归&#xff0c;而且是一个带路径的递归&#xff0c;那么递归大家一定很熟悉了&#xff0c;大学c语言课程里面就介绍过递归&#xff0c;我…

HepG2细胞复苏实验以及六孔板种植细胞实验

一、细胞复苏实验&#xff1a; 首先先用紫外照射复苏细胞的新培养皿&#xff0c;然后预热要用到的1640培养基&#xff08;控制在30mins以内&#xff0c;否则会发生蛋白质结构转变等&#xff09;&#xff0c;等待培养基预热完毕后。取出冻存的HepG2细胞&#xff0c;手拿头部在水…

springboot021-基于协同过滤算法的个性化音乐推荐系统

&#x1f495;&#x1f495;作者&#xff1a; 小九学姐 &#x1f495;&#x1f495;个人简介&#xff1a;十年Java&#xff0c;Python美女程序员一枚&#xff0c;精通计算机专业前后端各类框架。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xf…

Vue 3最新组件解析与实践指南:提升开发效率的利器

目录 引言 一、Vue 3核心组件特性解析 1. Composition API与组件逻辑复用 2. 内置组件与生命周期优化 3. 新一代UI组件库推荐 二、高级组件开发技巧 1. 插件化架构设计 2. 跨层级组件通信 三、性能优化实战 1. 惰性计算与缓存策略 2. 虚拟滚动与列表优化 3. Tree S…

github用户名密码登陆失效了

问题&#xff1a; git push突然推代码需要登陆&#xff0c;但是用户名和密码正确输入后&#xff0c;却提示403 git push# Username for https://github.com: **** #Password for https://gyp-programmergithub.com: #remote: Permission to gyp-programmer/my-app.git denie…

js考核第三题

题三&#xff1a;随机点名 要求&#xff1a; 分为上下两个部分&#xff0c;上方为显示区域&#xff0c;下方为控制区域。显示区域显示五十位群成员的学号和姓名&#xff0c;控制区域由开始和结束两个按钮 组成。点击开始按钮&#xff0c;显示区域里的内容开始滚动&#xff0c;…

如何在Flask中处理静态文件

哈喽,大家好,我是木头左! 本文将详细介绍如何在Flask中处理静态文件,包括如何配置静态文件夹、如何访问静态文件以及如何处理静态文件的缓存问题。 配置静态文件夹 在Flask中,你可以通过static_folder参数来指定静态文件夹。默认情况下,Flask会在项目的根目录下寻找名为…

Deep seek学习日记1

Deepseek最强大的就是它的深度思考&#xff0c;并且展现了它的思考过程。 五种可使用Deep seek的方式&#xff08;应该不限于这五种&#xff0c;后续嵌入deepseek的应该更多&#xff0c;多了解一点因为官网容易崩~~&#xff09;&#xff1a; 1.deep seek官网 2.硅基流动silicon…

电子电气架构 --- OEM对软件自研的期待

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

grep如何排除多个目录?

在使用 grep 进行文本搜索时&#xff0c;有时候需要排除多个目录&#xff0c;避免在这些目录下进行搜索。下面介绍几种不同的实现方式。 目录 1.使用 -r 和 --exclude-dir 选项&#xff08;GNU grep&#xff09; 2.使用扩展正则表达式和 -P 选项&#xff08;GNU grep&#x…

deepseek_各个版本django特性

以下是 Django 2.0 至 5.0 的主要区别总结&#xff0c;按版本特性分类说明&#xff1a; 1. Django 2.0 的主要变化 Python 支持 仅支持 Python 3.4&#xff0c;不再兼容 Python 2.x。路由系统 弃用 url()&#xff0c;引入 path() 和 re_path() 替代&#xff0c;path() 默认不支…

微信小程序中缓存数据全方位解惑

微信小程序中缓存数据全方位解惑 微信小程序中的数据缓存是提升用户体验和优化性能的重要手段&#xff0c;跟电脑浏览器中的Local Storage的性质一样。以下是关于微信小程序数据缓存的相关知识点和示例的详细介绍&#xff1a; 1. 数据缓存的类型 微信小程序提供了两种数据缓…

IP 路由基础 | 路由条目生成 / 路由表内信息获取

注&#xff1a;本文为 “IP 路由” 相关文章合辑。 未整理去重。 IP 路由基础 秦同学学学已于 2022-04-09 18:44:20 修改 一. IP 路由产生背景 我们都知道 IP 地址可以标识网络中的一个节点&#xff0c;并且每个 IP 地址都有自己的网段&#xff0c;各个网段并不相同&#xf…

kong身份认证插件详解之Basic Auth插件

1.3、Basic Authentication 支持基于用户名和密码的基本认证&#xff0c;通常用于简单的身份验证场景。 1.3.1、环境准备 1.3.1.1、创建一个服务&#xff0c;basic-auth-service curl -i -s -X POST http://localhost:8001/services \--data namebasic-auth-service \--dat…

计算机性能与网络体系结构探讨 —— 基于《计算机网络》谢希仁第八版

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

spring boot知识点1

1.什么是spring boot spring boot是spring框架的子项目&#xff0c;主要特点是自动配置&#xff0c;以及内置的tomcat服务器&#xff0c;适合快速开发web与微服务架构 2.spring boot和spring cloud俩者之间的联系 spring boot可单独运行&#xff0c; spring cloud则是用于多…

thingboard告警信息格式美化

原始报警json内容&#xff1a; { "severity": "CRITICAL","acknowledged": false,"cleared": false,"assigneeId": null,"startTs": 1739801102349,"endTs": 1739801102349,"ackTs": 0,&quo…

【个人开发】deepspeed+Llama-factory 本地数据多卡Lora微调

文章目录 1.背景2.微调方式2.1 关键环境版本信息2.2 步骤2.2.1 下载llama-factory2.2.2 准备数据集2.2.3 微调模式2.2.3.1 zero-3微调2.2.3.2 zero-2微调2.2.3.3 单卡Lora微调 2.3 踩坑经验2.3.1 问题一&#xff1a;ValueError: Undefined dataset xxxx in dataset_info.json.2…