数据结构——栈(详细分析)

目录

🍉引言

🍉栈的本质和特点

🍈栈的基本操作

🍈栈的特点

🍍后进先出

🍍操作受限

🍍动态调整

🍈栈的优缺点

🍍优点

🍍缺点

🍉栈的应用

栈的代码实现

代码说明

图解栈的出入过程

压栈过程

出栈过程

栈的实际应用实例

括号匹配

浏览器前进后退功能

代码说明

🍉栈的实现细节和优化

🍈数组实现栈的优化


🍉引言

栈(Stack)是一种常见的数据结构,在计算机科学中具有重要的应用价值。栈的操作受限于后进先出(LIFO, Last In First Out)的原则,这种特点使得栈在处理特定类型的问题时非常高效。本文将详细解析栈的本质和特点,并通过生活中的例子和代码实现来深入理解栈的应用。

🍉栈的本质和特点

  • 栈是一种线性数据结构,只允许在一端进行插入和删除操作,这一端称为栈顶(Top)。与栈顶相对的另一端称为栈底(Bottom),栈底是固定的,不进行操作

🍈栈的基本操作

栈有几种基本操作:

  1. 压栈(Push):将一个元素添加到栈顶。
  2. 出栈(Pop):移除并返回栈顶元素。
  3. 取栈顶元素(Peek or Top):返回栈顶元素但不移除它。
  4. 检查栈是否为空(isEmpty):返回布尔值,指示栈是否为空。
  5. 检查栈是否已满(isFull):返回布尔值,指示栈是否已满(主要用于固定大小的栈)。

🍈栈的特点

🍍后进先出

  • 栈的最主要特点是后进先出,即最新加入的元素最先被移除。这种特性使得栈特别适用于某些特定的应用场景。

🍍操作受限

  • 与其他数据结构相比,栈的操作比较受限。只能在栈顶进行压栈和出栈操作,不能直接访问栈中的任意元素。

🍍动态调整

  • 栈可以是固定大小的,也可以是动态调整大小的。动态栈会根据需要自动调整其容量。

🍈栈的优缺点

🍍优点

  1. 简单高效: 栈的操作简单,只需考虑栈顶元素,因此执行速度较快。

  2. 内存管理: 栈内存的分配和释放是自动进行的,不需要手动管理内存,避免了内存泄漏和垃圾回收的问题。

  3. 递归调用: 栈结构天然适合处理递归调用,函数的调用和返回都可以利用栈的特性。

🍍缺点

  1. 大小限制: 栈的大小通常是固定的,当数据量超过栈的容量时会导致栈溢出。

  2. 数据不灵活: 栈是一种后进先出(LIFO)的数据结构,只能在栈顶进行操作,不适合需要随机访问数据的场景。

  3. 局部性: 栈中的数据只能按照特定的顺序访问,缺乏灵活性,不能满足所有的数据操作需求。


🍉栈的应用

栈在现实生活和计算机科学中都有广泛的应用:

  1. 函数调用:计算机系统使用栈来管理函数调用。每次函数调用时,当前函数的状态(如局部变量、返回地址等)会被压入栈中。当函数返回时,状态从栈中弹出并恢复。
  2. 表达式求值和语法解析:栈用于将中缀表达式转换为后缀表达式或前缀表达式,并且在表达式求值过程中,栈也扮演重要角色。
  3. 浏览器的前进后退功能:浏览器使用两个栈来管理用户的浏览历史,一个栈存储前进的页面,另一个栈存储后退的页面。
  4. 撤销操作:许多软件(如文本编辑器、图像编辑器等)使用栈来实现撤销和恢复功能。

栈的代码实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Stack {int *items;int top;int capacity;
} Stack;// 初始化栈
Stack* createStack(int capacity) {Stack *stack = (Stack *)malloc(sizeof(Stack));stack->capacity = capacity;stack->top = -1;stack->items = (int *)malloc(capacity * sizeof(int));return stack;
}// 检查栈是否为空
bool is_empty(Stack *stack) {return stack->top == -1;
}// 压入元素到栈
void push(Stack *stack, int item) {if (stack->top == stack->capacity - 1) {printf("栈已满,无法压入元素\n");return;}stack->items[++stack->top] = item;
}// 弹出栈顶元素
int pop(Stack *stack) {if (is_empty(stack)) {printf("栈为空,无法弹出元素\n");exit(EXIT_FAILURE);}return stack->items[stack->top--];
}// 获取栈顶元素
int peek(Stack *stack) {if (is_empty(stack)) {printf("栈为空,无法获取栈顶元素\n");exit(EXIT_FAILURE);}return stack->items[stack->top];
}// 获取栈的大小
int size(Stack *stack) {return stack->top + 1;
}int main() {Stack *stack = createStack(100); // 创建一个容量为100的栈push(stack, 1);push(stack, 2);push(stack, 3);printf("栈顶元素:%d\n", peek(stack));  // 输出 3printf("出栈元素:%d\n", pop(stack));   // 输出 3printf("栈顶元素:%d\n", peek(stack));  // 输出 2printf("栈大小:%d\n", size(stack));    // 输出 2// 释放分配的内存free(stack->items);free(stack);return 0;
}

代码说明

  1. Stack 结构体包含一个整数数组 items,一个表示栈顶索引的 top,和一个表示栈容量的 capacity
  2. createStack 函数用于初始化栈并分配内存。
  3. is_empty 函数用于检查栈是否为空。
  4. push 函数用于将元素压入栈,并在栈满时打印错误信息。
  5. pop 函数用于弹出栈顶元素,并在栈为空时打印错误信息并退出程序。
  6. peek 函数用于获取栈顶元素,并在栈为空时打印错误信息并退出程序。
  7. size 函数用于获取栈的大小(当前存储的元素数量)。
  8. main 函数演示了栈的使用,类似于您提供的 Python 代码示例。

图解栈的出入过程

  • 为了更直观地理解栈的操作过程,我们可以使用图解的方式来展示栈的压栈和出栈操作。

压栈过程

  • 初始状态:栈为空
栈: []
  •  压栈 1:
压栈 1
栈: [1]
  • 压栈 2:
压栈 2
栈: [1, 2]
  • 压栈 3:
压栈 3
栈: [1, 2, 3]

出栈过程

  • 初始状态:栈顶元素为 3
栈: [1, 2, 3]
  • 出栈 3:
出栈 3
栈: [1, 2]
  • 出栈 2:
出栈 2
栈: [1]
  • 出栈 1:
出栈 1
栈: []

栈的实际应用实例

括号匹配

  • 括号匹配问题是栈的经典应用之一,常用于编译器和解释器的语法解析。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 栈结构定义
typedef struct Stack {int top;unsigned capacity;char* array;
} Stack;// 创建栈
Stack* createStack(unsigned capacity) {Stack* stack = (Stack*) malloc(sizeof(Stack));stack->capacity = capacity;stack->top = -1;stack->array = (char*) malloc(stack->capacity * sizeof(char));return stack;
}// 判断栈是否为空
int isEmpty(Stack* stack) {return stack->top == -1;
}// 入栈
void push(Stack* stack, char item) {stack->array[++stack->top] = item;
}// 出栈
char pop(Stack* stack) {if (isEmpty(stack))return '\0';return stack->array[stack->top--];
}// 获取栈顶元素
char peek(Stack* stack) {if (isEmpty(stack))return '\0';return stack->array[stack->top];
}// 判断括号是否平衡
int isBalanced(const char* expression) {Stack* stack = createStack(strlen(expression));char pairs[256] = { 0 };pairs[')'] = '(';pairs[']'] = '[';pairs['}'] = '{';for (int i = 0; i < strlen(expression); i++) {char char = expression[i];if (char == '(' || char == '{' || char == '[') {push(stack, char);} else if (char == ')' || char == '}' || char == ']') {if (isEmpty(stack) || pop(stack) != pairs[char]) {free(stack->array);free(stack);return 0; // false}}}int balanced = isEmpty(stack);free(stack->array);free(stack);return balanced;
}// 测试函数
int main() {const char* expression1 = "{[()()]}";printf("%s\n", isBalanced(expression1) ? "True" : "False");const char* expression2 = "{[(])}";printf("%s\n", isBalanced(expression2) ? "True" : "False");return 0;
}

浏览器前进后退功能

浏览器的前进和后退功能可以通过两个栈来实现,一个栈用于存储前进页面,另一个栈用于存储后退页面:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct Node {char* data;struct Node* next;
} Node;typedef struct Stack {Node* top;
} Stack;// 初始化栈
void init_stack(Stack* stack) {stack->top = NULL;
}// 检查栈是否为空
int is_empty(Stack* stack) {return stack->top == NULL;
}// 推入元素到栈
void push(Stack* stack, const char* data) {Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = (char*)malloc(strlen(data) + 1);strcpy(new_node->data, data);new_node->next = stack->top;stack->top = new_node;
}// 弹出栈顶元素
char* pop(Stack* stack) {if (is_empty(stack)) {return NULL;}Node* temp = stack->top;char* data = temp->data;stack->top = stack->top->next;free(temp);return data;
}// 浏览器历史记录结构体
typedef struct BrowserHistory {Stack forward_stack;Stack backward_stack;char* current_page;
} BrowserHistory;// 初始化浏览器历史记录
void init_browser_history(BrowserHistory* history) {init_stack(&history->forward_stack);init_stack(&history->backward_stack);history->current_page = NULL;
}// 访问新页面
void visit(BrowserHistory* history, const char* page) {if (history->current_page != NULL) {push(&history->backward_stack, history->current_page);}history->current_page = (char*)malloc(strlen(page) + 1);strcpy(history->current_page, page);// 清空前进栈while (!is_empty(&history->forward_stack)) {free(pop(&history->forward_stack));}
}// 后退
char* back(BrowserHistory* history) {if (!is_empty(&history->backward_stack)) {push(&history->forward_stack, history->current_page);history->current_page = pop(&history->backward_stack);return history->current_page;} else {return NULL;  // 无法后退}
}// 前进
char* forward(BrowserHistory* history) {if (!is_empty(&history->forward_stack)) {push(&history->backward_stack, history->current_page);history->current_page = pop(&history->forward_stack);return history->current_page;} else {return NULL;  // 无法前进}
}int main() {BrowserHistory history;init_browser_history(&history);visit(&history, "Page1");visit(&history, "Page2");visit(&history, "Page3");printf("%s\n", back(&history));  // 输出 Page2printf("%s\n", back(&history));  // 输出 Page1printf("%s\n", forward(&history));  // 输出 Page2return 0;
}

代码说明

  1. 栈的实现:我们用 Node 结构体来表示栈中的节点,用 Stack 结构体来管理栈顶节点。提供了初始化、检查空栈、推入和弹出元素的函数。
  2. 浏览器历史记录:用 BrowserHistory 结构体来管理当前页面和两个栈。提供了初始化、访问新页面、后退和前进的函数。
  3. 内存管理:使用 mallocfree 来动态分配和释放内存,以避免内存泄漏。

🍉栈的实现细节和优化

  • 在实际应用中,栈的实现可以有多种方式,包括使用数组(列表)或链表。每种方式都有其优缺点:
  1. 数组实现栈:简单高效,但需要预先确定栈的最大容量,可能会导致空间浪费或溢出。
  2. 链表实现栈:灵活,无需预先确定栈的容量,但每个节点需要额外的指针存储空间,操作相对复杂。

🍈数组实现栈的优化

  • 为了解决数组实现栈的容量限制问题,可以使用动态数组,它会在需要时自动扩展容量:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct {int *items;int capacity;int size;
} DynamicArrayStack;// Initialize the stack
DynamicArrayStack* createStack() {DynamicArrayStack* stack = (DynamicArrayStack*)malloc(sizeof(DynamicArrayStack));stack->capacity = 1;stack->size = 0;stack->items = (int*)malloc(stack->capacity * sizeof(int));return stack;
}// Check if the stack is empty
bool isEmpty(DynamicArrayStack* stack) {return stack->size == 0;
}// Push an item onto the stack
void push(DynamicArrayStack* stack, int item) {if (stack->size == stack->capacity) {stack->capacity *= 2;stack->items = (int*)realloc(stack->items, stack->capacity * sizeof(int));}stack->items[stack->size++] = item;
}// Pop an item from the stack
int pop(DynamicArrayStack* stack) {if (isEmpty(stack)) {fprintf(stderr, "pop from empty stack\n");exit(EXIT_FAILURE);}return stack->items[--stack->size];
}// Peek at the top item of the stack
int peek(DynamicArrayStack* stack) {if (isEmpty(stack)) {fprintf(stderr, "peek from empty stack\n");exit(EXIT_FAILURE);}return stack->items[stack->size - 1];
}// Get the size of the stack
int stackSize(DynamicArrayStack* stack) {return stack->size;
}// Free the stack
void freeStack(DynamicArrayStack* stack) {free(stack->items);free(stack);
}int main() {// Create and use the dynamic array stackDynamicArrayStack* dynamicStack = createStack();push(dynamicStack, 1);push(dynamicStack, 2);push(dynamicStack, 3);printf("栈顶元素:%d\n", peek(dynamicStack)); // 输出 3printf("出栈元素:%d\n", pop(dynamicStack));  // 输出 3printf("栈顶元素:%d\n", peek(dynamicStack)); // 输出 2printf("栈大小:%d\n", stackSize(dynamicStack)); // 输出 2freeStack(dynamicStack);return 0;
}

希望这些能对刚学习算法的同学们提供些帮助哦!!!

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

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

相关文章

SQOOP详细讲解

SQOOP安装及使用 SQOOP安装及使用SQOOP安装1、上传并解压2、修改文件夹名字3、修改配置文件4、修改环境变量5、添加MySQL连接驱动6、测试准备MySQL数据登录MySQL数据库创建student数据库切换数据库并导入数据另外一种导入数据的方式使用Navicat运行SQL文件导出MySQL数据库impo…

数据结构------二叉树经典习题2

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1.非递归的前序遍历 1.用栈来实现 2,前序遍历是根左右, 先是根节点入栈,,然后不为空时向左遍历,当为空时就返回向右遍历,右为空时直接出栈,依次循环即可. public void preOrderNot(TreeNode root){Stack<TreeNo…

科技赋能,打破视障人士的沟通壁垒

在探索如何增强盲人群体的社会参与度与幸福感的旅程中&#xff0c;盲人社交能力提升策略成为了不容忽视的一环。随着科技的不断进步&#xff0c;像“蝙蝠避障”这样的辅助软件&#xff0c;不仅在日常出行中为盲人提供了实时避障和拍照识别的便利&#xff0c;也在无形中为他们拓…

华为数通 HCIP-Datacom(H12-821)题库

最新 HCIP-Datacom&#xff08;H12-821&#xff09;完整题库请扫描上方二维码访问&#xff0c;持续更新中。 BGP路由的Update消息中可不包含以下哪些属性&#xff1f; A、Local Preference B、AS Path C、MED D、Origin 答案&#xff1a;AC 解析&#xff1a;as-path和ori…

Java17 --- SpringCloud之Sentinel

目录 一、Sentinel下载并运行 二、创建8401微服务整合Sentinel 三、流控规则 3.1、直接模式 3.2、关联模式 3.3、链路模式 3.3.1、修改8401代码 3.3.2、创建流控模式 3.4、Warm UP&#xff08;预热&#xff09; ​编辑 3.5、排队等待 四、熔断规则 4.1、慢调用比…

【C++】09.vector

一、vector介绍和使用 1.1 vector的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改…

操作系统实验四 (综合实验)设计简单的Shell程序

前言 因为是一年前的实验&#xff0c;很多细节还有知识点我都已经遗忘了&#xff0c;但我还是尽可能地把各个细节讲清楚&#xff0c;请见谅。 1.实验目的 综合利用进程控制的相关知识&#xff0c;结合对shell功能的和进程间通信手段的认知&#xff0c;编写简易shell程序&…

Excel透视表:快速计算数据分析指标的利器

文章目录 概述1.数据透视表基本操作1.1准备数据&#xff1a;1.2创建透视表&#xff1a;1.3设置透视表字段&#xff1a;1.4多级分类汇总和交叉汇总的差别1.5计算汇总数据&#xff1a;1.6透视表美化&#xff1a;1.7筛选和排序&#xff1a;1.8更新透视表&#xff1a; 2.数据透视-数…

【B站 heima】小兔鲜Vue3 项目学习笔记Day02

文章目录 Pinia1.使用2. pinia-计数器案例3. getters实现4. 异步action5. storeToRefsx 数据解构保持响应式6. pinia 调试 项目起步1.项目初始化和git管理2. 使用ElementPlus3. ElementPlus 主题色定制4. axios 基础配置5. 路由设计6. 静态资源初始化和 Error lens安装7.scss自…

Github 2024-05-24 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-24统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目3非开发语言项目2TypeScript项目2JavaScript项目1Kotlin项目1C#项目1C++项目1Shell项目1Microsoft PowerToys: 最大化Windows系统生产…

软件设计师备考笔记(十):网络与信息安全基础知识

文章目录 一、网络概述二、网络互连硬件&#xff08;一&#xff09;网络的设备&#xff08;二&#xff09;网络的传输介质&#xff08;三&#xff09;组建网络 三、网络协议与标准&#xff08;一&#xff09;网络的标准与协议&#xff08;二&#xff09;TCP/IP协议簇 四、Inter…

某神,云手机启动?

某神自从上线之后&#xff0c;热度不减&#xff0c;以其丰富的内容和独特的魅力吸引着众多玩家&#xff1b; 但是随着剧情无法跳过&#xff0c;长草期过长等原因&#xff0c;近年脱坑的玩家多之又多&#xff0c;之前米家推出了一款云某神的app&#xff0c;目标是为了减少用户手…

RedisTemplateAPI:String

文章目录 ⛄1 String 介绍⛄2 命令⛄3 对应 RedisTemplate API❄️❄️ 3.1 添加缓存❄️❄️ 3.2 设置过期时间(单独设置)❄️❄️ 3.3 获取缓存值❄️❄️ 3.4 删除key❄️❄️ 3.5 顺序递增❄️❄️ 3.6 顺序递减 ⛄4 以下是一些常用的API⛄5 应用场景 ⛄1 String 介绍 Str…

ue引擎游戏开发笔记(47)——设置状态机解决跳跃问题

1.问题分析&#xff1a; 目前当角色起跳时&#xff0c;只是简单的上下移动&#xff0c;空中仍然保持行走动作&#xff0c;并没有设置跳跃动作&#xff0c;因此&#xff0c;给角色设置新的跳跃动作&#xff0c;并优化新的动作动画。 2.操作实现&#xff1a; 1.实现跳跃不复杂&…

Java中的继承和多态

继承 在现实世界中&#xff0c;狗和猫都是动物&#xff0c;这是因为他们都有动物的一些共有的特征。 在Java中&#xff0c;可以通过继承的方式来让对象拥有相同的属性&#xff0c;并且可以简化很多代码 例如&#xff1a;动物都有的特征&#xff0c;有名字&#xff0c;有年龄…

Mybatis源码剖析---第一讲

Mybatis源码剖析 基础环境搭建 JDK8 Maven3.6.3&#xff08;别的版本也可以…&#xff09; MySQL 8.0.28 --> MySQL 8 Mybatis 3.4.6 准备jar&#xff0c;准备数据库数据 把依赖导入pom.xml中 <properties><project.build.sourceEncoding>UTF-8</p…

Linux学习笔记:线程

Linux中的线程 什么是线程线程的使用原生线程库创建线程线程的id线程退出等待线程join分离线程取消一个线程线程的局部存储在c程序中使用线程使用c自己封装一个简易的线程库 线程互斥(多线程)导致共享数据出错的原因互斥锁关键函数pthread_mutex_t :创建一个锁pthread_mutex_in…

雷电预警监控系统:守护安全的重要防线

TH-LD1在自然界中&#xff0c;雷电是一种常见而强大的自然现象。它既有震撼人心的壮观景象&#xff0c;又潜藏着巨大的安全风险。为了有效应对雷电带来的威胁&#xff0c;雷电预警监控系统应运而生&#xff0c;成为现代社会中不可或缺的安全防护工具。 雷电预警监控系统的基本…

makefile 编写规则

1.概念 1.1 什么是makefile Makefile 是一种文本文件&#xff0c;用于描述软件项目的构建规则和依赖关系&#xff0c;通常用于自动化软件构建过程。它包含了一系列规则和指令&#xff0c;告诉构建系统如何编译和链接源代码文件以生成最终的可执行文件、库文件或者其他目标文件…

Node.js知识点以及案例总结

思考&#xff1a;为什么JavaScript可以在浏览器中被执行 每个浏览器都有JS解析引擎&#xff0c;不同的浏览器使用不同的JavaScript解析引擎&#xff0c;待执行的js代码会在js解析引擎下执行 为什么JavaScript可以操作DOM和BOM 每个浏览器都内置了DOM、BOM这样的API函数&#xf…