7. 栈

news2025/1/27 12:42:18

栈(stack)是一种遵循先入后出的逻辑的线性数据结构。我们可以将栈类比为桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈数据结构。

如下图所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫做“入栈”,删除栈顶元素的操作叫做“出栈”。

7.1 栈常用操作

栈的常用操作如下表所示,具体的方法名需要根据所使用的编程语言来确定。在此,我们以常见的 push()pop()peek() 命名为例。

 通常情况下,我们可以直接使用编程语言内置的栈类。然而,某些语言可能没有专门提供栈类,这时我们可以将该语言的“数组”或“链表”视作栈来使用,并在程序逻辑上忽略与栈无关的操作。

/* 初始化栈 */
stack<int> stack;

/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);

/* 访问栈顶元素 */
int top = stack.top();

/* 元素出栈 */
stack.pop(); // 无返回值

/* 获取栈的长度 */
int size = stack.size();

/* 判断是否为空 */
bool empty = stack.empty();

7.2 栈的实现

为了深入了解栈的运行机制,我们来尝试自己实现一个栈类。

栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,因此栈可以被视为一种受限制的数组或链表。换句话说,我们可以“屏蔽”数组或链表的部分无关操作,使其对外表现的逻辑符合栈的特性。

7.2.1 基于链表的实现

使用链表来实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。

如下图所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。

 以下是基于链表实现栈的示例代码。

/* 基于链表实现的栈 */
class LinkedListStack {
  private:
    ListNode *stackTop; // 将头节点作为栈顶
    int stkSize;        // 栈的长度

  public:
    LinkedListStack() {
        stackTop = nullptr;
        stkSize = 0;
    }

    ~LinkedListStack() {
        // 遍历链表删除节点,释放内存
        freeMemoryLinkedList(stackTop);
    }

    /* 获取栈的长度 */
    int size() {
        return stkSize;
    }

    /* 判断栈是否为空 */
    bool isEmpty() {
        return size() == 0;
    }

    /* 入栈 */
    void push(int num) {
        ListNode *node = new ListNode(num);
        node->next = stackTop;
        stackTop = node;
        stkSize++;
    }

    /* 出栈 */
    void pop() {
        int num = top();
        ListNode *tmp = stackTop;
        stackTop = stackTop->next;
        // 释放内存
        delete tmp;
        stkSize--;
    }

    /* 访问栈顶元素 */
    int top() {
        if (isEmpty())
            throw out_of_range("栈为空");
        return stackTop->val;
    }

    /* 将 List 转化为 Array 并返回 */
    vector<int> toVector() {
        ListNode *node = stackTop;
        vector<int> res(size());
        for (int i = res.size() - 1; i >= 0; i--) {
            res[i] = node->val;
            node = node->next;
        }
        return res;
    }
};

7.2.2 基于数组的实现

使用数组实现栈时,我们可以将数组的尾部作为栈顶。如下图所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 O(1) 。

 由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码。

/* 基于数组实现的栈 */
class ArrayStack {
  private:
    vector<int> stack;

  public:
    /* 获取栈的长度 */
    int size() {
        return stack.size();
    }

    /* 判断栈是否为空 */
    bool isEmpty() {
        return stack.size() == 0;
    }

    /* 入栈 */
    void push(int num) {
        stack.push_back(num);
    }

    /* 出栈 */
    void pop() {
        int oldTop = top();
        stack.pop_back();
    }

    /* 访问栈顶元素 */
    int top() {
        if (isEmpty())
            throw out_of_range("栈为空");
        return stack.back();
    }

    /* 返回 Vector */
    vector<int> toVector() {
        return stack;
    }
};

7.3 两种实现对比

支持操作

两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。

时间效率

在基于数组的实现中,入栈和出栈操作都是在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为O(n) 。

在链表实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。

综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 int 或 double ,我们可以得出以下结论。

  • 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。
  • 基于链表实现的栈可以提供更加稳定的效率表现。

空间效率

在初始化列表时,系统会为列表分配“初始容量”,该容量可能超过实际需求。并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容,扩容后的容量也可能超出实际需求。因此,基于数组实现的栈可能造成一定的空间浪费

然而,由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大

综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。

7.4 栈典型应用

  • 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会将上一个网页执行入栈,这样我们就可以通过后退操作回到上一页面。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
  • 程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会执行出栈操作。

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

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

相关文章

分析某款go端口扫描器之一

一、概述 进来在学go的端口检测部分&#xff0c;但是自己写遇到很多问题&#xff0c;又不知道从何入手&#xff0c;故找来网上佬们写的现成工具&#xff0c;学习一波怎么实现的。分析过程杂乱&#xff0c;没啥思路&#xff0c;勿喷。 项目来源&#xff1a;https://github.com/…

毫米波传感器系统性能测量(TI文档)

摘要 本应用报告讨论了使用德州仪器高性能毫米波传感器的系统性能测量结果。TI的毫米波传感器是一个77 GHz&#xff0c;高度集成的收发器&#xff0c;具有高速接口(CSI2)&#xff0c;可将原始ADC数据发送出去进行处理。毫米波传感器包括整个毫米波射频和模拟基带信号链&#xf…

异步操作的方法

在高级语言中已经有了异步的原语言&#xff0c;而在C 中的最好的方式就是 libevent 的方式,我这还是相当认同的&#xff0c;高级语言就不需要在苦哈哈的&#xff0c;事件转圈了&#xff0c;但是原理还是以事件为基础的 一句话就是在一个循环中等着他执行完,这个循环中有很多其他…

论文阅读:Distributed Initialization for VVIRO with Position-Unknown UWB Network

前言 Distributed Initialization for Visual-Inertial-Ranging Odometry with Position-Unknown UWB Network这篇论文是发表在ICRA 2023上的一篇文章&#xff0c;本文提出了一种基于位置未知UWB网络的一致性视觉惯性紧耦合优化测距算法( DC-VIRO )的分布式初始化方法。 对于…

【LeetCode刷题-字符串】--71.简化路径

71.简化路径 思路&#xff1a; 对于给定的字符串&#xff0c;先根据/分割成一个由若干字符串组成的列表&#xff0c;记为names&#xff0c;根据题意names中包含的字符串只能是以下几种&#xff1a; 空字符串一个点两个点只包含英文字母、数字或_的目录名 对于空字符串和一个…

Linux小程序之进度条

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;自己能实现进度条 > 毒鸡汤&#xff1a; > …

绘制折扇-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第11讲。 绘制折扇&#xf…

基于ssm亚盛汽车配件销售业绩管理系统

摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让亚盛汽车配件销售信息的管理模式进行升级&#xff0c;也为了更好的维护亚盛汽车配件销售信息&#xff0c;亚盛汽车配件销售业绩管理系统的开…

牛客 算法题 【HJ102 字符统计】 golang实现

题目 HJ102 字符统计 golang代码实现 package mainimport ("bufio""fmt""os""sort" )func main() {// str_arry :make([]string, 0)str_map : make(map[rune]int)result_map : make(map[int][]string)scanner : bufio.NewScanner(os…

Python函数定义、函数调用详解

函数是 Python 程序的重要组成单位&#xff0c;一个 Python 程序可以由很多个函数组成。前面我们己经用过大量函数&#xff0c;如 len()、max() 等&#xff0c;使用函数是真正开始编程的第一步。 比如在程序中定义了一段代码&#xff0c;这段代码用于实现一个特定的功能。问题来…

注册Zoho Mail邮箱:优势与使用体验

如何注册Zoho Mail邮箱&#xff1f;要注册Zoho Mail邮箱&#xff0c;首先打开浏览器&#xff0c;访问Zoho Mail官网&#xff0c;点击页面右上角的“创建帐户”按钮。接下来&#xff0c;按照提示输入你的姓名、生日和性别&#xff0c;以及一个有效的手机号码或电子邮件地址。然后…

数据库管理-第118期 记一次开启附加日志导致的性能问题(202301129)

数据库管理-第118期 记一次开启附加日志导致的性能问题&#xff08;202301129&#xff09; 本周二凌晨&#xff0c;为了配合某国产数据库从Oracle数据库能够实时同步数据&#xff0c;在X9M那套一体机上做了开启附加日志的操作&#xff0c;也正是因为这个操作带来了一些小问题。…

构建现代Web应用:5个基本的前端架构原则

本文翻译自 Building modern Web Applications: 5 Essential Frontend Architecture Principles&#xff0c;作者&#xff1a;Patrick Roos&#xff0c; 略有删改。 在这篇文章中&#xff0c;我提出了构建现代前端的五个架构原则。我第一次听到这些原则是在Natalia Venditto的一…

NTT 的各类优化:Harvey、PtNTT,Intel AVX2、ARM Neon、GPGPU

参考文献&#xff1a; [Har14] Harvey D. Faster arithmetic for number-theoretic transforms[J]. Journal of Symbolic Computation, 2014, 60: 113-119.[Sei18] Seiler G. Faster AVX2 optimized NTT multiplication for Ring-LWE lattice cryptography[J]. Cryptology ePr…

QDoubleSpinBox的使用示例

QDoubleSpinBox即可以做为数值型输入框使用&#xff0c;也可以使用只读型数据显示框&#xff0c;在作为输入框使用时比QLineEdit有以下几个方面的优势 1.可以设置范围&#xff0c;并且范围精确&#xff0c; 2.输入数据精确&#xff0c;自动屏幕非数值以外的字符。 3.设置步长后…

【LeetCode刷题】--77.组合

77.组合 class Solution {public List<List<Integer>> combine(int n, int k) {List<List<Integer>> ans new ArrayList<>();if( k < 0 || n < k){return ans;}Deque<Integer> list new ArrayDeque<>();dfs(ans,list,n,k,1)…

Panorama SCADA平台助力智能建筑管理,掌控未来建筑!

来源&#xff1a;宏集科技 工业物联网 宏集方案 Panorama SCADA平台助力智能建筑管理&#xff0c;掌控未来建筑&#xff01; 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 前言 在现代智能建筑管理中&#xff0c;随着设施管理&#xff08;FM&#xff09;、建筑管理…

05_MySQL主从复制架构

任务背景 ##一、真实案例 某同学刚入职公司&#xff0c;在熟悉公司业务环境的时候&#xff0c;发现他们的数据库架构是一主两从&#xff0c;但是两台从数据库和主库不同步。询问得知&#xff0c;已经好几个月不同步了&#xff0c;但是每天会全库备份主服务器上的数据到从服务…

Hadoop数据仓库平台搭建

在这里是学习大数据的第一站 什么是数据仓库常见大数据平台组件及介绍 什么是数据仓库 在计算领域&#xff0c;数据仓库&#xff08;DW 或 DWH&#xff09;也称为企业数据仓库&#xff08;EDW&#xff09;&#xff0c;是一种用于报告和数据分析的系统&#xff0c;被认为是商业智…

Mysql安全之基础合规

一、背景 某次某平台进行安全性符合型评估时&#xff0c;列出了数据库相关安全选项&#xff0c;本文特对此记录&#xff0c;以供备忘参考。 二、安全配置 2.1、数据库系统登录时的用户进行身份标识和鉴别&#xff1b; 1&#xff09;对登录Mysql系统用户的密码复杂度是否有要…