​​​【收录 Hello 算法】5.1 栈

目录

5.1   栈

5.1.1   栈的常用操作

5.1.2   栈的实现

1.   基于链表的实现

2.   基于数组的实现

5.1.3   两种实现对比

5.1.4   栈的典型应用


5.1   栈

栈(stack)是一种遵循先入后出逻辑的线性数据结构。

我们可以将栈类比为桌面上的一摞盘子,如果想取出底部的盘子,则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈这种数据结构。

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

栈的先入后出规则

图 5-1   栈的先入后出规则

5.1.1   栈的常用操作

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

表 5-1   栈的操作效率

方法描述时间复杂度
push()元素入栈(添加至栈顶)𝑂(1)
pop()栈顶元素出栈𝑂(1)
peek()访问栈顶元素𝑂(1)

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

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRubyZig

stack.cpp

/* 初始化栈 */
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();

5.1.2   栈的实现

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

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

1.   基于链表的实现

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

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

LinkedListStackpush()pop()

基于链表实现栈的入栈出栈操作

图 5-2   基于链表实现栈的入栈出栈操作

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

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRubyZig

linkedlist_stack.cpp

/* 基于链表实现的栈 */
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++;}/* 出栈 */int pop() {int num = top();ListNode *tmp = stackTop;stackTop = stackTop->next;// 释放内存delete tmp;stkSize--;return num;}/* 访问栈顶元素 */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;}
};

2.   基于数组的实现

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

ArrayStackpush()pop()

基于数组实现栈的入栈出栈操作

图 5-3   基于数组实现栈的入栈出栈操作

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

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRubyZig

array_stack.cpp

/* 基于数组实现的栈 */
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);}/* 出栈 */int pop() {int num = top();stack.pop_back();return num;}/* 访问栈顶元素 */int top() {if (isEmpty())throw out_of_range("栈为空");return stack.back();}/* 返回 Vector */vector<int> toVector() {return stack;}
};

5.1.3   两种实现对比

支持操作

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

时间效率

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

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

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

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

空间效率

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

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

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

5.1.4   栈的典型应用

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

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

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

相关文章

Linux diff命令(比较两个文件或目录的内容差异)

文章目录 Linux diff 命令详解教程基本用法比较文件输出解释 递归比较&#xff08;-r&#xff09;示例代码 控制输出格式统一格式&#xff08;-u&#xff09;上下文格式&#xff08;-c&#xff09; 高级选项忽略所有空白差异&#xff08;-w&#xff09;仅报告文件是否不同 Linu…

SVN 合并到 Git 时有文件大于 100 M 被限制 Push

如果有文件大小大于 100M&#xff0c;GitHub 是会被限制推送到仓库中的&#xff0c;大概率情况会显示下面的错误&#xff1a; remote: Resolving deltas: 100% (3601/3601), done. remote: error: Trace: aea1f450da6f2ef7bfce457c715d0fbb9b0f6d428fdca80233aff34b601ff59b re…

RobbitMQ基本消息队列的消息发送过程

RabbitMQ: One broker to queue them all | RabbitMQ RabbitMQ官网 SpringAmqp的官方地址&#xff1a;Spring AMQP 代码示例:对着代码看应该能看明白 publisher:消息发送者的代码示例 package cn.itcast.mq.helloworld;import com.rabbitmq.client.Channel; import com.rabb…

线路和绕组中的波过程(一)

本篇为本科课程《高电压工程基础》的笔记。 本篇为这一单元的第一篇笔记。下一篇传送门。 当电路中的设备&#xff08;元件&#xff09;最大实际尺寸l大于人们所感兴趣的谐波波长 λ \lambda λ时&#xff0c;可以作为集中参数处理&#xff0c;否则就要当做分布参数处理。即&…

基于STM32移植lvgl(V8.2)(SPI接口的LCD)

目录 概述 1 认识LVGL 1.1 LVGL官网 1.2 LVGL库文件下载 2 认识SPI接口型LCD 2.1 PIN引脚定义 2.2 MCU IO与LCD PIN对应关系 3 实现LCD驱动 3.1 使用STM32Cube配置Project 3.2 STM32Cube生成工程 4 移植LVGL 4.1 准备移植文件 4.2 添加lvgl库文件到项目 4.2.1 src下…

linux fdisk 银河麒麟操作系统 v10 磁盘分区和挂载 详细教程

1查看 未加载的磁盘 fdisk -l 2 开始分区 fdisk /dev/vdb #查看分区 #新建分区和保存 3 格式化和挂载 fdisk -l mkfs.xfs /dev/vdb1 #查看uuid blkid /dev/vdb1 mkdir /data vi /etc/fstab UUID209daa-fb1c-48f2-bf5e-e63f38cb8a /data xfs defaults 0 0 #加载下 mo…

探秘未来科技:数字化无人巡检的奇妙之旅

嘿&#xff0c;朋友们&#xff01;下午茶时间到&#xff01;趁着这会儿咱们来聊一个超级炫酷的话题——数字化无人巡检。想象一下&#xff0c;那些曾经需要人工跋山涉水、风吹日晒的巡检工作&#xff0c;现在正被一群“智能小分队”悄悄接手&#xff0c;是不是觉得既神奇又方便…

Ascent DMS AE电源说明书和设备连接调试教程

Ascent DMS AE电源说明书和设备连接调试教程

彩虹聚合DNS管理系统

聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析&#xff0c;目前已支持的域名平台有&#xff1a;阿里云、腾讯云、华为云、西部数码、CloudFlare。本系统支持多用户&#xff0c;每个用户可分配不同的域名解析权限&#xff1b;支持API接口&#xff0c;支持获取域名…

FFmpeg常用API与示例(三)—— 音视频解码与编码

编解码层 1.解码 (1) 注册所有容器格式和 CODEC:av_register_all() (2) 打开文件:av_open_input_file() (3) 从文件中提取流信息:av_find_stream_info() (4) 穷举所有的流&#xff0c;查找其中种类为 CODEC_TYPE_VIDEO (5) 查找对应的解码器:avcodec_find_decoder() (6) …

19.接口自动化-Jekins学习

1.CI-持续集成 频繁的&#xff08;一天多次&#xff09;将代码集成到主干 目的&#xff1a;让产品快速迭代&#xff0c;保持高质量 好处&#xff1a; 快速发现错误&#xff0c;每次更新都集成到主干&#xff0c;可以快速发现错误&#xff0c;定位错误也容易防止分支大幅偏离主…

QT C++(QWidget类及其常见的属性)

文章目录 1. QWidget类及其常见的属性 1. QWidget类及其常见的属性 QT各种控件都是继承自QWidget类&#xff0c;QWidget类是QT控件体系中通用的部分。 QWidget属性如下图 常见的QT属性为&#xff1a; enabled&#xff1a;描述控件是否处于可用状态&#xff08;禁用状态这个…