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

目录

🍉引言

🍉栈的本质和特点

🍈栈的基本操作

🍈栈的特点

🍍后进先出

🍍操作受限

🍍动态调整

🍈栈的优缺点

🍍优点

🍍缺点

🍉栈的应用

栈的代码实现

代码说明

图解栈的出入过程

压栈过程

出栈过程

栈的实际应用实例

括号匹配

浏览器前进后退功能

代码说明

🍉栈的实现细节和优化

🍈数组实现栈的优化


🍉引言

栈(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));  // 输出 3
    printf("出栈元素:%d\n", pop(stack));   // 输出 3
    printf("栈顶元素:%d\n", peek(stack));  // 输出 2
    printf("栈大小:%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));  // 输出 Page2
    printf("%s\n", back(&history));  // 输出 Page1
    printf("%s\n", forward(&history));  // 输出 Page2

    return 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 stack
    DynamicArrayStack* dynamicStack = createStack();
    push(dynamicStack, 1);
    push(dynamicStack, 2);
    push(dynamicStack, 3);

    printf("栈顶元素:%d\n", peek(dynamicStack)); // 输出 3
    printf("出栈元素:%d\n", pop(dynamicStack));  // 输出 3
    printf("栈顶元素:%d\n", peek(dynamicStack)); // 输出 2
    printf("栈大小:%d\n", stackSize(dynamicStack)); // 输出 2

    freeStack(dynamicStack);
    return 0;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32F407VET6 学习笔记4:DAC数模转换功能的配置

今日继续学习使用嘉立创的 立创梁山派天空星&#xff0c;芯片是 STM32F407VET6 使用库函数编程 最近突然发现很久没有接触过单片机的AD转换功能了&#xff0c;之前还是学习51单片机时学习驱动PCF8591芯片实现AD转换功能的&#xff0c;还从未在STM32平台上进行过相关的实验经验…

解决go install 网络问题

rootiZbp1hiqzlhh6w05gloffgZ:~# go install mvdan.cc/garblelatest go: mvdan.cc/garblelatest: module mvdan.cc/garble: Get "https://proxy.golang.org/mvdan.cc/garble/v/list": dial tcp 172.217.160.81:443: i/o timeout解决方法 更换阿里代理 rootiZbp1hiq…

保障餐饮场所安全:定期送检可燃气体报警器

在餐饮行业&#xff0c;火灾隐患一直备受关注。餐厅、茶饮店等场所常常使用燃气设备&#xff0c;而这些设备带来了潜在的安全隐患。 为了及时发现并预防可燃气体泄漏&#xff0c;可燃气体报警器的定期送检显得尤为重要。那么&#xff0c;为什么可燃气体报警器需要定期送检呢&a…

python中的线程并行

文章目录 1. 单线程2. 线程池ThreadPoolExecutor 1. 单线程 现在有1154张图片需要顺时针旋转后保存到本地&#xff0c;一般使用循环1154次处理&#xff0c;具体代码如下所示&#xff0c;img_paths中存储1154个图片路径&#xff0c;该代码段耗时约用97ms。 t1time.time() for …

【再探】设计模式—代理模式

代理是指授权代理人在一定范围内代表其向第三方进行处理有关事务。 1 代理模式 需求&#xff1a;1&#xff09;将业务代码与非业务代码分离&#xff0c;在不改变代码结构的基础上&#xff0c;为其添加新的功能。2&#xff09;为系统中的某些操作做同一处理&#xff0c;例如进…

Dilworth 定理

这是一个关于偏序集的定理&#xff0c;事实上它也可以扩展到图论&#xff0c;dp等中&#xff0c;是一个很有意思的东西 偏序集 偏序集是由集合 S S S以及其上的一个偏序关系 R R R定义的&#xff0c;记为 ( S , R ) (S,R) (S,R) 偏序关系&#xff1a; 对于一个二元关系 R ⊂…

Python筑基之旅-MySQL数据库(四)

目录 一、数据表操作 1、新增记录 1-1、用mysql-connector-python库 1-2、用PyMySQL库 1-3、用PeeWee库 1-4、用SQLAlchemy库 2、删除记录 2-1、用mysql-connector-python库 2-2、用PyMySQL库 2-3、用PeeWee库 2-4、用SQLAlchemy库 3、修改记录 3-1、用mysql-conn…

力扣HOT100 - 21. 合并两个有序链表

解题思路&#xff1a; class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode dum new ListNode(0), cur dum;while (list1 ! null && list2 ! null) {if (list1.val < list2.val) {cur.next list1;list1 list1.next;} els…

力扣刷题---3146. 两个字符串的排列差

题目描述 给你两个字符串 s 和 t&#xff0c;每个字符串中的字符都不重复&#xff0c;且 t 是 s 的一个排列。 排列差 定义为 s 和 t 中每个字符在两个字符串中位置的绝对差值之和。 返回 s 和 t 之间的 排列差 。 示例 1&#xff1a; 输入&#xff1a;s “abc”, t “b…

四万字长文详解——node.js使用移动云,EOS对象存储

目录 前言 安装及安装前的操作 前置条件 如何创建认证信息 使用npm安装SDK开发包 安装开发包命令 初始化操作 存储桶 查看结果命令 查看桶列表 查看结果命令 删除桶 查看结果命令 创建桶 获取桶列表 判断桶是否存在 查询桶所属地域 查询桶的访问权限 管理桶的…

基于springboot+vue+Mysql的校园台球厅人员与设备管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【C语言】冒泡排序详解

前言 排序&#xff0c;就是将一组数据按特定的规则调换位置&#xff0c;使这组数据具有某种顺序关系&#xff0c;一般就是递增或递减。 在接触C语言不久&#xff0c;我们就会遇到其中一种有名的排序算法——“冒泡排序”&#xff0c;不知道你是否已经掌握了&#xff0c;如果还…

2024最新 Jenkins + Docker 实战教程(五)- 配置Gitee Webhooks实现自动构建部署

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

等保建设:打造MySQL数据库审计系统

1、建设目标 在等级保护三级->应用安全->安全审计中强制需要有审计平台(满足对操作系统、数据库、网络设备的审计&#xff0c;在条件不允许的情况下&#xff0c;至少要使用数据库审计) 数据库审计服务符合等级保护三级标准&#xff0c;帮助您满足合规性要求&#xff0c;…

什么是组态?什么是工业控制中的组态软件?

随着工业4.0和智能制造的发展&#xff0c;工控软件的应用越来越广泛&#xff0c;它们在提高生产效率、降低能耗和减少人力成本等方面发挥着越来越重要的作用。 什么是工控软件&#xff1f; 工控软件是指用于工业控制系统的软件&#xff0c;主要应用于各种生产过程控制、自动化…

Java中流的概念细分

按流的方向分类&#xff1a; 输入流&#xff1a;数据流向是数据源到程序&#xff08;以InputStream、Reader结尾的流&#xff09;。 输出流&#xff1a;数据流向是程序到目的地&#xff08;以OutputStream、Writer结尾的流&#xff09;。 按处理的数据单元分类&#xff1a; 字…

在winnas中使用docker desktop遇到的问题及解决方法记录

最近在尝试从群晖转向winnas&#xff0c;一些简单的服务依然计划使用docker来部署。群晖的docker简单易用且稳定&#xff0c;在win上使用docker desktop过程中遇到了不少问题&#xff0c;在此记录一下以供后来人参考。 一、安装docker desktop后启动时遇到无法启动docker引擎 …

构建数字未来:探索Web3在物联网中的新视角

引言 随着Web3时代的来临&#xff0c;物联网技术正迎来一场新的变革。在这个数字化时代&#xff0c;Web3所带来的技术创新将为物联网的发展开辟新的视角。本文将深入探讨Web3在物联网领域的应用&#xff0c;揭示其在构建数字未来中的重要性和影响。 Web3与物联网的融合 区块链…

运用HTML、CSS设计Web网页——“西式甜品网”图例及代码

目录 一、效果展示图 二、设计分析 1.整体效果分析 2.头部header模块效果分析 3.导航及banner模块效果分析 4.分类classify模块效果分析 5.产品展示show模块效果分析 6.版权banquan模块效果分析 三、HTML、CSS代码分模块展示 1. 头部header模块代码 2.导航及bann…

QQ个性网空间日志网站模板源码

QQ个性网空间日志网站模板源码自带后台登录设置&#xff0c;适用于博客、文章、资讯、其他类网站内容使用。模板自带eyoucms内核&#xff0c;原创设计、手工书写DIVCSS&#xff0c;完美兼容IE7、Firefox、Chrome、360浏览器等;主流浏览器;结构容易优化;多终端均可正常预览。由于…