【数据结构】栈(基于数组、链表实现 + GIF图解 + 原码)

news2024/11/15 12:39:45

Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
🌱🌱个人主页:奋斗的明志
🌱🌱所属专栏:数据结构

在这里插入图片描述

📚本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。

在这里插入图片描述

文章目录

  • 前言
  • 一、栈(Stack)
    • 1.概念
    • 2.栈在现实生活中的例子
  • 二、栈的使用
    • 1.方法
    • 2.代码
  • 三、栈的模拟实现
    • 1.入栈图解
    • 2.出栈图解
    • 3.数组实现的栈
    • 4.链表实现的栈
    • 5.push(链表实现)
    • 6.pop(链表实现)
  • 四、栈的应用场景
    • 1.改变元素的序列
    • 2.将递归转化为循环
      • 2.1 递归方式
      • 2.2 循环方式
  • 五、了解中缀表达式、后缀表达式
  • 总结


前言

一、栈(Stack)

1.概念

  • :一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO( Last In First Out)的原则。
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
  • 出栈:栈的删除操作叫做出栈。 出数据在栈顶
  • 栈顶栈底: 这个描述是偏向于逻辑上的内容,因为大家知道数组在末尾插入删除更容易,而单链表通常在头插入删除更容易。所以数组可以用末尾做栈顶,而链表可以头做栈顶

2.栈在现实生活中的例子

在这里插入图片描述

在这里插入图片描述

栈的应用广泛,比如你的程序执行查看调用堆栈、计算机四则加减运算、算法的非递归形式、括号匹配问题等等。所以栈也是必须掌握的一门数据结构。最简单大家都经历过,你拿一本书上下叠在一起,就是一个后进先出的过程,你可以把它看成一个栈。下面我们介绍数组实现的栈链表实现的栈

二、栈的使用

1.方法

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空

2.代码

代码如下(示例):

public static void main(String[] args) {
    Stack<Integer> s = new Stack();
    s.push(1);
    s.push(2);
    s.push(3);
    s.push(4);
    System.out.println(s.size());  // 获取栈中有效元素个数---> 4 System.out.println(s.peek());  // 获取栈顶元素---> 4
    s.pop();  // 4出栈 ,栈中剩余1   2   3 ,栈顶元素为3
    System.out.println(s.pop());  // 3出栈 ,栈中剩余1  2  栈顶元素为3 if(s.empty()){
    if (s.empty()) {
        System.out.println("栈空");
    } else {
        System.out.println(s.size());
    }
}

三、栈的模拟实现

在这里插入图片描述
从上图中可以看到, Stack继承了Vector VectorArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。

1.入栈图解

在这里插入图片描述

2.出栈图解

在这里插入图片描述

3.数组实现的栈

代码如下(示例):

package stackdemo;

import java.util.Arrays;

public class MyStack {
    //用什么来组织呢?
    // 数组、链表
    // 目前先用数组

    //先创建数组
    public int[] elem;
    public int usedSize;//表示有效个数,也可以当下标使用
    public static final int DEFAULT_CAPACITY = 10;

    public MyStack() {
        //初始化数组容量
        this.elem = new int[DEFAULT_CAPACITY];
    }

    //压栈  入栈
    public void push(int val){
        if (isFull()){
            //扩容
            this.elem = Arrays.copyOf(elem, 2 * elem.length);

        }
        elem[usedSize++] = val;
    }

    /**
     * 判断数组是否满了
     */
    public boolean isFull(){
        return usedSize == this.elem.length;
    }


    /**
     * 出栈
     * @return
     */
    public int pop(){
        if (isEmpty()){
            throw new EmptyStackException("栈为空");
        }
        usedSize--;
        return elem[usedSize];
    }

    public boolean isEmpty(){
        return usedSize == 0;
    }

    public int peek(){
        if (isEmpty()){
            throw new EmptyStackException("栈为空");
        }
        return elem[usedSize - 1];
    }
}

测试类

import stackdemo.MyStack;

import java.util.LinkedList;
import java.util.Stack;

public class Test {
    public static void main(String[] args) {
        LinkedList<Integer> stack = new LinkedList<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);

        System.out.println(stack.pop());

        System.out.println(stack.peek());

    }

    public static void main01(String[] args) {
//        Stack<Integer> stack = new Stack<>();
        MyStack stack = new MyStack();
        //向栈里面添加元素

        stack.push(12);
        stack.push(23);
        stack.push(34);
        stack.push(45);
        //先进后出

        //出栈,有两个方法
        // pop 弹出,有一个返回值 直接从栈里面删除元素

        int ret = stack.pop();
        System.out.println(ret);//45

        // peek 也有返回值
        // peek 只是获取栈顶元素 ,不删除
        // 元素还在栈里面
        int peek = stack.peek();
        System.out.println(peek);

        // 判断栈空不空
        System.out.println(stack.isEmpty());

    }
}

4.链表实现的栈

像数组那样在尾部插入删除。大家都知道链表效率低在查询,而查询到尾部效率很低,就算用了尾指针,可以解决尾部插入效率,但是依然无法解决删除效率(删除需要找到前驱节点),还需要双向链表。前面虽然详细介绍过双向链表,但是这样未免太复杂!

所以我们先采用带头节点的单链表在头部插入删除,把头当成栈顶,插入直接在头节点后插入,删除也直接删除头节点后第一个节点即可,这样就可以完美的满足栈的需求。

代码如下(示例):

package stackdemo;


public class lisStack<T> {
    
    static class Node<T> {
        T data;
        Node next;

        public Node() {
        }

        public Node(T value) {
            this.data = value;
        }
    }

    int length;
    Node<T> head;//头节点

    public lisStack() {
        head = new Node<>();
        length = 0;
    }

    boolean isEmpty() {
        return head.next == null;
    }

    int length() {
        return length;
    }

    public void push(T value) {//近栈
        Node<T> team = new Node<T>(value);
        if (length == 0) {
            head.next = team;
        } else {
            team.next = head.next;
            head.next = team;
        }
        length++;
    }

    public T peek() throws Exception {
        if (length == 0) {
            throw new Exception("链表为空");
        } else {//删除
            return (T) head.next.data;
        }
    }

    public T pop() throws Exception {//出栈
        if (length == 0) {
            throw new Exception("链表为空");
        } else {//删除
            T value = (T) head.next.data;
            head.next = head.next.next;//va.next
            length--;
            return value;
        }
    }

    public String toString() {
        if (length == 0) {
            return "";
        } else {
            String va = "";
            Node team = head.next;
            while (team != null) {
                va += team.data + " ";
                team = team.next;
            }
            return va;
        }
    }
}



5.push(链表实现)

push插入

与单链表头插入一致,如果不太了解可以看看前面写的线性表有具体讲解过程。

和数组形成的栈有个区别,链式实现的栈理论上栈没有大小限制(不突破内存系统限制),不需要考虑是否越界,而数组则需要考虑容量问题。

  • 如果一个节点team入栈:
  • 空链表入栈head.next=team;
  • 非空入栈team.next=head.next;head.next=team;

在这里插入图片描述

6.pop(链表实现)

pop弹出

与单链表头删除一致,如果不太了解请先看前面单链表介绍的。

和数组同样需要判断栈是否为空,如果节点team出栈:head指向team后驱节点。

在这里插入图片描述

四、栈的应用场景

1.改变元素的序列

  1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈 ,则下列不可能的一个出栈序列是 ()

    A: 1,4,3,2
    B: 2,3,4,1
    C: 3,1,4,2
    D: 3,4,2,1

  2. 一个栈的初始状态为空。现将元素1、2、3、4、5、A、 B、C、 D、 E依次入栈 ,然后再依次出栈 ,则元素出栈的顺 序是( )。

    A: 12345ABCDE
    B: EDCBA54321
    C: ABCDE12345
    D: 54321EDCBA

2.将递归转化为循环

2.1 递归方式

思路解析:

  • 如果 head 不为 null,递归调用 printList(head.next) 先递归到链表的末尾。
  • 当递归回溯时,打印当前节点 head 的值。

工作原理:

  • 当 printList(head.next) 运行到链表末尾时,开始逐层回溯。
  • 每次回溯时,会依次打印每个节点的值,实现了链表的逆序输出。
// 递归方式
void printList(Node head) {
    if (null != head) {
        printList(head.next);
        System.out.print(head.val + " ");
    }
}


2.2 循环方式

思路解析:

  • 如果 head 为 null,直接返回。
  • 使用一个栈 s 来存储链表中的节点。
  • 遍历链表,将每个节点依次压入栈中。
  • 最后,依次弹出栈中的节点并打印其值,实现了链表的逆序输出。

工作原理:

  • 遍历链表的过程中,将节点依次压入栈中,因为栈的特性是后进先出(LIFO)。
  • 当遍历完成后,栈中的节点顺序是链表的逆序。
  • 依次弹出栈中的节点并打印,即可实现链表元素值的逆序输出。
// 循环方式
void printList(Node head) {
    if (null == head) {
        return;
    }

    Stack<Node> s = new Stack<>();
// 将链表中的结点保存在栈中
    Node cur = head;
    while (null != cur) {
        s.push(cur);
        cur = cur.next;
    }
    // 将栈中的元素出栈
    while (!s.empty()) {
        System.out.print(s.pop().val + " ");
    }
}

五、了解中缀表达式、后缀表达式

  • 下面以 a + b * c + ( d * e + f ) * g 为例子

  • 讲下应该怎么把中缀表达式转换成后缀表达式。

  • 按先加减后乘除的原则给表达式加括号

  • 结果:((a+(bc))+(((de)+f)*g))

  • 由内到外把每个括号里的表达式换成后缀

  • 最终结果:a b c * + d e * f + g * +

  • 这样就得到了中缀表达式转后缀表达式的最终结果。

  • 此法应付考试有神效。

总结

LinkedKist 就可以当做栈来使用

  • 递归方式:简单、优雅,但可能会面临栈溢出的风险,特别是在链表非常长的情况下。
  • 循环方式:使用了额外的栈来辅助逆序输出,空间复杂度略高,但是可以避免递归深度过深导致的栈溢出问题。

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

Linux--Socket 编程 UDP(简单的回显服务器和客户端代码)

目录 0.上篇文章 1.V1 版本 - echo server 1.1认识接口 1.2实现 V1 版本 - echo server&#xff08;细节&#xff09; 1.3添加的日志系统&#xff08;代码&#xff09; 1.4 解析网络地址 1.5 禁止拷贝逻辑&#xff08;基类&#xff09; 1.6 服务端逻辑 &#xff08;代码&…

Leetcode—769. 最多能完成排序的块【中等】

2024每日刷题&#xff08;149&#xff09; Leetcode—769. 最多能完成排序的块 实现代码 class Solution { public:int maxChunksToSorted(vector<int>& arr) {int ans 0;int mx INT_MIN;for(int i 0; i < arr.size(); i) {mx max(arr[i], mx);if(mx i) {a…

【C++】C++应用案例-旋转图像

旋转图像的需求&#xff0c;在图片处理的过程中非常常见。我们知道对于计算机而言&#xff0c;图像其实就是一组像素点的集合&#xff0c;所以图像旋转的问题&#xff0c;本质上就是一个二维数组的旋转问题。 我们可以给定一个二维数组&#xff0c;用来表示一个图像&#xff0c…

【C++】——红黑树(手撕红黑树,彻底弄懂红黑树)

目录 前言 一 红黑树简介 二 为什么需要红黑树 三 红黑树的特性 四 红黑树的操作 4.1 变色操作 4.2 旋转操作 4.3 插入操作 4.4 红黑树插入代码实现 4.5 红黑树的删除 五 红黑树迭代器实现 总结 前言 我们之前都学过ALV树&#xff0c;AVL树的本质就是一颗平…

Oracle对比两表数据的不一致

MINUS 基本语法如下 [SQL 语句 1] MINUS [SQL 语句 2];举个例子&#xff1a; select 1 from dual minus select 2 from dual--运行结果 1-------------------------------- select 2 from dual minus select 1 from dual--运行结果 2所以&#xff0c;如果想找所有不一致的&a…

软件测试---Linux

Linux命令使用&#xff1a;为了将来工作中与服务器设备进行交互而准备的技能&#xff08;远程连接/命令的使用&#xff09;数据库的使用&#xff1a;MySQL&#xff0c;除了查询动作需要重点掌握以外&#xff0c;其他操作了解即可什么是虚拟机 通过虚拟化技术&#xff0c;在电脑…

富芮坤FR800X系列之按键检测模块设计

FR800X系列按键检测模块 读者对象&#xff1a; 本文档主要适用以下工程师&#xff1a; 嵌入式系统工程师 单片机软件工程师 IOT固件工程师 BLE固件工程师 文章目录 1.概要2.用户如何设计按键检测模块2.1 GPIO初始化2.2按键模块初始化2.3设计中断函数&#xff1a;2.4循环…

【Python面试题收录】Python编程基础练习题①(数据类型+函数+文件操作)

本文所有代码打包在Gitee仓库中https://gitee.com/wx114/Python-Interview-Questions 一、数据类型 第一题&#xff08;str&#xff09; 请编写一个Python程序&#xff0c;完成以下任务&#xff1a; 去除字符串开头和结尾的空格。使用逗号&#xff08;","&#…

【数据库】Quartz2.3 框架 数据库设计说明书

1、 Quartz表说明 2、 quartz 的触发时间的配置 1、 cron 方式&#xff1a;采用cronExpression表达式配置时间。 2、 simple 方式&#xff1a;和JavaTimer差不多&#xff0c;可以指定一个开始时间和结束时间外加一个循环时间。 3、 calendars 方式&#xff1a;可以和cron配合使…

Java-----栈

目录 1.栈&#xff08;Stack&#xff09; 1.1概念 1.2栈的使用 1.3栈的模拟实现 1.4栈的应用场景 1.5栈、虚拟机栈、栈帧有什么区别呢 1.栈&#xff08;Stack&#xff09; 1.1概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操…

Centos8 yum 更换源以及安装内核头文件

文章目录 一、简介二、yum 更换源三、安装内核头文件 一、简介 CentOS 是一个开源项目&#xff0c;发布了两个不同的 Linux 发行版——CentOS Stream 和 CentOS Linux 。 CentOS Stream 是即将发布的红帽企业 Linux 产品的上游开发平台。 CentOS 项目将于 2024 年 6 月 30 日…

场外期权如何报价?名义本金是什么?

今天带你了解场外期权如何报价&#xff1f;名义本金是什么&#xff1f;投资者首先需要挑选自己想要进行期权交易的沪深上市公司股票。选出股票后&#xff0c;需要将股票信息、预期的操作时间&#xff08;如期限&#xff09;、看涨或看跌的选择以及预计的交易金额等信息报给场外…

商家虚假发货行为频发,电商平台如何通过物流轨迹来监管?(内附视频号、抖音、京东的发货规则)

近年来&#xff0c;“虚假发货”问题在电商行业中日益凸显。某投诉平台数据显示&#xff0c;截至2024年7月&#xff0c;搜索“虚假发货”显示的投诉高达19万条&#xff0c;如何有效监控卖家发货的合规性与及时性、打击虚假发货行为成为电商平台的重要议题。 为了维护消费者权益…

剧透:巴黎奥运会用上了AI转播

** AI增强技术&#xff0c;让比赛画面变成电影特效。 ** 巴黎奥运会即将开幕&#xff01; 阿里云在奥运转播中应用的AI增强技术 将让比赛画面变成电影特效&#xff01; 剧透如下 &#x1f447;&#x1f3fb; 阿里云为奥运转播提供的高自由度回放“子弹时间”&#xff0c;是…

[Mysql-DDL数据操作语句]

目录 DDL语句操作数据库 库&#xff1a; 查看&#xff1a;show 创建&#xff1a;creat 删除&#xff1a;drop 使用(切换)&#xff1a;use 表&#xff1a; 查看&#xff1a;desc show 创建&#xff1a;create 表结构修改 rename as add drop modify change rename as …

cesium海洋到站提示

项目地址:Every Admin: 用于快速搭建后台管理和其他页面的项目,组件化开发,以及大屏展示. <template> <div class"topbox"> xx海洋管理 </div> <div class"selectbox"> <div class"title"> 航线列表 </div>…

了解Java虚拟机(JVM)

前言&#x1f440;~ 上一章我们介绍网络原理相关的知识点&#xff0c;今天我们浅浅来了解一下java虚拟机JVM JVM&#xff08; Java Virtual Machine &#xff09; JVM内存区域划分 方法区/元数据区&#xff08;线程共享&#xff09; 堆&#xff08;线程共享&#xff09; 虚…

Nginx 配置与优化:常见问题全面解析

文章目录 Nginx 配置与优化:常见问题全面解析一、Nginx 安装与配置问题1.1 Nginx 安装失败问题描述解决方法1.2 Nginx 配置文件语法错误问题描述解决方法二、Nginx 服务启动与停止问题2.1 Nginx 无法启动问题描述解决方法2.2 Nginx 服务无法停止问题描述解决方法三、Nginx 性能…

尚硅谷vue全家桶(vue2+vue3)笔记

Vue2 一、Vue核心 01_简介 1.特点 采用组件化模式&#xff0c;提高代码复用率、且让代码更好维护。声明式编码&#xff0c;让编程人员无需直接操作DOM&#xff08;命令式编码&#xff09;&#xff0c;提高开发效率。使用虚拟DOM优秀的Diff算法&#xff0c;尽量复用DOM节点。…

【日常记录】【JS】JS中查询参数处理工具URLSearchParams

文章目录 1. 引言2. URLSearchParams2.1 URLSearchParams 的构造函数2.2 append() 方法2.3 delete() 方法2.4 entries() 方法2.5 forEach() 方法2.6 get() 方法2.7 getAll() 方法2.8 has() 方法2.9 keys() 方法2.10 set() 方法2.11 toString() 方法2.12 values() 方法 参考链接…