【数据结构】栈和队列的深度探索,从实现到应用详解

news2024/11/22 16:23:06

在这里插入图片描述

💎所属专栏:数据结构与算法学习 

💎 欢迎大家互三:2的n次方_

🍁1. 栈的介绍

栈是一种后进先出的数据结构,栈中的元素只能从栈顶进行插入和删除操作,类似于叠盘子,最后放上去的盘子最先拿下来。

🍁2. 栈的基本操作

  • 压栈(Push):将一个元素压入栈顶。
  • 出栈(Pop):移除并返回栈顶元素。
  • 栈顶元素(Peek):返回栈顶元素但不移除。
  • 判空(IsEmpty):检查栈是否为空。
  • 栈的大小(Size):返回栈中的元素个数。

栈的定义方法也是和ArrayList一样的,然后就是使用对象去调用栈的方法

public class Text {
    public static void main(String[] args) {
        Stack<Integer> stack1 = new Stack<>();
        stack1.push(1);
        stack1.push(2);
        stack1.push(3);
        System.out.println(stack1.pop());
        System.out.println(stack1.peek());
        System.out.println(stack1.isEmpty());
        System.out.println(stack1.size());
    }
}

🍁3. 栈的实现

首先,栈是通过数组实现的,就像之前实现的顺序表一样

public class MyStack {
    public int[] elem;
    public int usedSize;

    public MyStack() {
        this.elem = new int[10];
    }
}

接下来实现一些栈的基本操作

🍁3.1 push()

当1 2 3 4依次入栈时,如下图

入栈其实很简单,只需要把元素放进去,接着usedSize++就可以了,但是学习数据结构我们的思维要严谨,如果栈满了怎么办,所以还需要处理栈满的情况,栈满之后就扩容,扩容也是和之前的顺序表一样的,判断是否栈满了也很简单,只需要判断数组的长度和usedSize是否相等就可以了

   public void push(int val) {
        if(isFull()) {
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
   }
   public boolean isFull() {
        return usedSize == elem.length;
   }

🍁3.2 pop()

遵循后进先出的原则,第一次出栈取到的元素是4

那怎么去实现这个效果呢

只需要定义一个val,把栈顶的元素取出来,赋值给val,进行返回,同时出栈之后usedSize的值要减1

之后还需要考虑栈为空的情况,如果栈为空,肯定是不能再进行出栈的操作了,此时就需要抛出一个异常

    public int pop() {
        if(isEmpty()){
            throw new EmptyStackException();
        }
        int val = elem[usedSize - 1];
        usedSize--;
        return val;
    }
    public boolean isEmpty() {
        return usedSize == 0;
    }

判断栈为空只需要判断usedSize是否为空

🍁3.3 peek()

peek()是获取栈顶元素,但是不删除,这个其实更简单,只需要把下标为usedSize的元素进行返回就可以了,也不需要usedSize--

同时,还是需要处理一下栈为空的情况

    public int peek() {
        if(usedSize == 0){
            throw new EmptyStackException();
        }
        return elem[usedSize - 1];
    }

接下来把之前写的方法测试一下:

public class Text {
    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        System.out.print(stack.pop() + " ");
        System.out.print(stack.pop() + " ");
        System.out.print(stack.peek() + " ");
    }
}

🍁4. 栈的使用场景

🍁4.1 链表实现栈

问题:单链表是否可以实现栈?

单链表其实是可以实现栈的,用链表实现的栈叫做链式栈

入栈:如果采用尾插法入栈,入栈的时间复杂度为O(n),如果给出last节点为O(1),出栈的时间复杂度为O(n),因为需要遍历到末尾才能入栈

如果采用头插法,入栈和出栈的时间复杂度都是O(1)

既然单链表可以实现栈了,那么双向链表肯定也可以实现栈,不论是头插还是尾插,双链表实现的栈出栈和入栈时间复杂度都是O(1)。

而且会发现,LinkedList也定义了栈的一些基本操作,可以当作栈来使用

🍁4.2 将递归转化为循环

一个典型的例子就是逆序打印链表,我们都知道,正常情况下,单链表是不能逆序打印的,递归的调用就类似于栈,最外面的一层先被打印,也就是最末尾元素最先打印,还可以通过栈来模拟递归

    //递归方式
    private void printList1(MySingleList.ListNode head){
        if(head!=null){
            printList1(head.next);
            System.out.print(head.value + " ");
        }
    }
    //循环方式
    private void printList2(MySingleList.ListNode head){
        if(head == null) return;
        Stack<MySingleList.ListNode> s = new Stack<>();
        MySingleList.ListNode cur = head;
        //将节点保存在栈中
        while(cur!=null){
            s.push(cur);
            cur = cur.next;
        }
        //打印栈中节点
        while(!s.empty()){
            System.out.print(s.pop().value + " ");
        }
    }

🍁5. 队列的介绍

队列是一种先进先出的数据结构。队列中的元素只能从队尾插入,从队首移除,类似于排队买票,最先排队的人最先买到票。

 Java中的Queue是一个接口,Deque叫做双端队列

🍁6. 队列的基本操作

  1. 入队(offer):将一个元素插入队尾。
  2. 出队(poll):移除并返回队首元素。
  3. 队首元素(Peek):返回队首元素但不移除。
  4. 判空(IsEmpty):检查队列是否为空。
  5. 队列的大小(Size):返回队列中的元素个数。

由于Queue是一个接口,不能直接创建对象,所以这里通过LinkedList来创建对象 

public class Text {
    public static void main(String[] args) {
        //使用接口的实现类LinkedList创建对象
        Queue<Integer> q = new LinkedList<>();
        q.offer(1);
        q.offer(2);
        q.offer(3);
        //出队
        System.out.println(q.poll());
        //获取对头元素
        System.out.println(q.peek());
    }
}

🍁7. 队列的实现

🍁7.1 双向链表实现队列

双向链表的话,和栈一样,入队和出队的操作时间复杂度为O(n),因为队列是先进先出的原则,入队就采用尾插的方法,出队也就是头删的方法

public class MyQueue {
    //双向链表
    static class ListNode {
        public int val;
        ListNode pre;
        ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

    ListNode first = null;
    ListNode last = null;
    public int usedSize = 0;
    //尾插的方法进行入队
    public void offer(int val) {
        ListNode node = new ListNode(val);
        if (isEmpty()) {
            first = last = node;
        } else {
            last.next = node;
            node.pre = last;
            last = last.next;
        }
        usedSize++;
    }
    //头删的方法进行出队
    public int poll() {
        if (first == null) {
            throw new EmptyQueueException("队列为空");
        }
        int value = first.val;
        first = first.next;
        if(first!=null){
            first.pre = null;
        }
        usedSize--;
        return value;
    }
    //获取队头元素
    public int peek() {
        if (first == null) {
            throw new EmptyQueueException("队列为空");
        }
        return first.val;
    }

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

🍁7.2 数组实现的循环队列

如果采用正常的数组来实现队列的话就会有以下的弊端,

 

这样出队之后,数组前面的空间就会空出来,造成空间的浪费,那如何把这些空间也利用起来呢

使用这样的循环结构,就可以解决这个问题,也就是循环队列

front和rear在同一个位置时,表示队列为空,那么队列满了也是这样的情况,此时怎么区分呢?

1.定义一个size专门判断

2.添加标记,定义一个boolean类型的flag,表示走过没有

3.空出一个空间,此时rear.next == front就表示队列已满 

此时还有一个问题:对于以上的例子,怎么解决下标的问题,例如从7下标是怎么到0下标的

 也就是下标最后再往后怎么表示:

公式:(数组下标 - 偏移量) % 数组长度 

7 ~ 0  可以通过 (7 + 1)% 8 来表示

还有一种情况:下标最前再往前

例如 2 下标往前走到 7 下标

公式:(数组下标 + 数组长度 - 偏移量) % 数组长度 

(2 + 9 - 4)% 9,加上数组长度也就是为了避免负数的情况

代码

测试用例

测试用例

测试结果

622. 设计循环队列

我们通过力扣上的这道题来实现一下:

class MyCircularQueue {
    public int front;
    public int rear;
    public int[] elem;

    public MyCircularQueue(int k) {
        elem = new int[k + 1];//由于有一个位置空出来了,所以要额外再多一个位置
    }

    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        elem[rear] = value;
        rear = (rear + 1) % elem.length;
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % elem.length;
        return true;
    }

    public int Front() {
        if (isEmpty()) {
            return -1;
        } else {
            return elem[front];
        }
    }

    public int Rear() {
        if (isEmpty())
            return -1;
        int index = (rear == 0 )? elem.length - 1 : rear - 1;
        return elem[index];
    }

    public boolean isEmpty() {
        return rear == front;
    }

    public boolean isFull() {
        return (rear + 1) % elem.length == front;
    }
}

🍁8. 双端队列 

在Java中,Deque(双端队列)是一个接口,它扩展了Queue接口。Deque支持在两端插入和删除元素,提供了比Queue更丰富的操作集合。可以使用Deque作为栈(后进先出)、队列(先进先出)、或者两者兼有。

Java提供了几种Deque的实现,其中最常见的是ArrayDequeLinkedListArrayDeque是基于数组的双端队列,它在大多数操作中都提供了更好的性能。而LinkedList也实现了Deque接口,但由于其基于链表的结构,它在添加和删除元素时可能不如ArrayDeque高效。

public class DequeDemo {
    public static void main(String[] args) {
        //线性实现
        Deque<Integer> deque = new ArrayDeque<>();
        deque.add(1);
        deque.add(2);
        deque.add(3);
        for(int i : deque){
            System.out.println(i);
        }
        //链式实现
        Deque<Integer> ldeque = new LinkedList<>();
        ldeque.add(1);
        ldeque.add(1);
        ldeque.add(1);
        for(int i : ldeque){
            System.out.println(i);
        }
    }
}

在这里插入图片描述

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

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

相关文章

Git代码管理工具 — 4 Git分支详解

目录 1 Git 分支概念 2 Git 分支基本操作 2.1 git branch查看与创建本地分支 2.2 git checkout切换分支 2.3 git merge合并分支 2.4 删除分支 3 解决冲突 1 Git 分支概念 Git 分支允许你从当前开发线上分离出来&#xff0c;进行独立的开发工作&#xff0c;而不会影响主…

用 AI 写歌词,让音乐表达与众不同

在音乐的广袤天地中&#xff0c;我们都渴望通过独特的表达来触动人心&#xff0c;展现自我。而如今&#xff0c;AI 技术的崛起为音乐创作带来了全新的突破&#xff0c;让我们能够以一种前所未有的方式赋予音乐独特的灵魂。 “妙笔生词智能写歌词软件&#xff08;veve522&#…

Window10下安装WSL-Ubuntu20.04

1.开启并更新WSL 1.1开启WSL 首先先来看一下电脑是否能够开启WSL:待补充... 然后再来看一下如何开启WSL:win->设置->应用->应用和功能->程序和功能&#xff0c;如下所示&#xff1a; 最后选择启用或关闭Windows功能&#xff0c;开启两个选项&#xff1a;1.Hyper-V…

大语言模型诞生过程剖析

过程图如下 &#x1f4da; 第一步&#xff1a;海量文本的无监督学习 得到基座大模型&#x1f389; &#x1f50d; 原料&#xff1a;首先&#xff0c;我们需要海量的文本数据&#xff0c;这些数据可以来自互联网上的各种语料库&#xff0c;包括书籍、新闻、科学论文、社交媒体帖…

K8S系列-Kubernetes基本概念及Pod、Deployment、Service的使用

一、Kubernetes 的基本概念和术语 一、资源对象 ​ Kubernetes 的基本概念和术语大多是围绕资源对象 Resource Object 来说的&#xff0c;而资源对象在总体上可分为以下两类: 1、某种资源的对象 ​ 例如节点 Node) Pod 服务 (Service) 、存储卷 (Volume&#xff09;。 2、…

记录些Redis题集(4)

Redis 通讯协议(RESP) Redis 通讯协议&#xff08;Redis Serialization Protocol&#xff0c;RESP&#xff09;是 Redis 服务端与客户端之间进行通信的协议。它是一种二进制安全的文本协议&#xff0c;设计简洁且易于实现。RESP 主要用于支持客户端和服务器之间的请求响应交互…

Adminer-CVE-2021-21311

在其4.0.0到4.7.9版本之间&#xff0c;连接 ElasticSearch 和 ClickHouse 数据库时存在一处服务端请求伪造漏洞&#xff08;SSRF&#xff09;。 VPS开启HTTP服务 VPS 开启HTTP 再同时跑POC 确保能访问poc里的链接文件 第一是目标地址 第二个是跳转地址 第三个是监听地址 如果…

昇思25天学习打卡营第21天|DCGAN生成漫画头像

DCGAN原理 DCGAN&#xff08;深度卷积对抗生成网络&#xff0c;Deep Convolutional Generative Adversarial Networks&#xff09;是GAN的直接扩展。不同之处在于&#xff0c;DCGAN会分别在判别器和生成器中使用卷积和转置卷积层。 它最早由Radford等人在论文Unsupervised Re…

【软件建模与设计】-03-软件生存周期模型和过程

目录 1、瀑布模型 2、抛弃型原型 3、演化式-增量模型 4、螺旋模型 5、统一软件开发过程RUP 6、设计验证和确认 6.1、软件质量保证 6.2、软件设计的性能分析 7、软件生存周期的活动 7.1、需求分析和规约 7.2、体系结构设计 7.3、详细设计 7.4、编码 8、软件测试 …

element-ui 插槽自定义样式怎么居中

场景&#xff1a;使用element-ui组件&#xff0c;scope内部自定义样式导致的错位 效果图&#xff1a; 解决思路&#xff1a; template标签可理解为一个内嵌组件&#xff0c;宽高重新定义&#xff0c;可在自定义内容外层套一层盒子&#xff0c;让盒子占满所有空间&#xff0c;再…

DBA 数据库管理 表管理 数据批量处理。表头约束

表管理 建库 库名命名规则&#xff1a;仅可以使用数字、字母、下划线、不能纯数字 不可使用MySQL命令或特殊字符 库名区分字母大小写 加if not exists 命令避免重名报错 create database if not exists gamedb; 建表 drop database if exists gamedb ; 删表…

从JDK源码探究Java线程与操作系统的交互

文章目录 从JDK源码探究Java线程与操作系统的交互一、序言二、线程基础概念1、操作系统线程实现方式&#xff08;1&#xff09;内核级线程&#xff08;Kernel-Level Thread&#xff09;&#xff08;2&#xff09;用户级线程&#xff08;User-Level Thread&#xff09;&#xff…

【DevOps系列】DevOps简介及基础环境安装

作者&#xff1a;后端小肥肠 目录 1. 前言 2. DevOps&#xff08;详细介绍&#xff09; 3. Code阶段工具 3.1 Git安装 3.2 GitLab安装 4. Build阶段工具 5. Operate阶段工具 5.1 Docker安装 5.2 Docker-Compose安装 6. Integrate工具 6.1 Jenkins介绍 6.2 Jenkins安…

8-1 搭建solidity开发环境,自己定制一个truffle

8-1 搭建solidity开发环境&#xff0c;自己定制一个truffle&#xff08;react区块链实战&#xff09; 从零开始搭建一个项目 自己实现一套类似truffle的自动编译系统&#xff0c;加深理解 此处可以跳过无需自己实现编译合约的模块&#xff0c;使用已有的truffle模块即可 项目…

SSM框架学习笔记(仅供参考)

&#xff08;当前笔记简陋&#xff0c;仅供参考&#xff09; 第一节课&#xff1a; &#xff08;1&#xff09;讲述了Spring框架&#xff0c;常用jar包&#xff0c;以及框架中各个文件的作用 &#xff08;2&#xff09;演示了一个入门程序 &#xff08;3&#xff09;解释了…

TS 入门(二):Typescript类型与类型注解

目录 前言回顾1. 基本类型数字类型 (number)字符串类型 (string)布尔类型 (boolean)空值和未定义 (null 和 undefined)任意类型 (any)unknown 类型any 与 unkown 区别 2. 数组和元组类型数组类型元组类型 3. 枚举类型4. 类型注解示例指定变量类型函数参数和返回值类型注解类型推…

在浏览器控制台中输出js对象,为什么颜色不同,有深有浅

打开console&#xff0c;输入自定义的javascript对象的时候&#xff0c;打开看发现对象的属性是深紫色&#xff0c;后面有一些对象是浅紫色的&#xff0c;比如Array对象和一堆SVG,HTML,CSS开头的对象&#xff0c;常用的prototype和__proto__也是浅紫色的。 请问这里深紫和浅紫…

9. Python3 Numpy科学计算库

Numpy是Python科学计算库的基础&#xff0c;主要包括&#xff1a; 强大的N维数组对象和向量运算。一些复杂的功能。与C和FORTRAN代码的集成。实用的线性代数运算、傅里叶变换、随机数生成等。 9.1 Numpy基础 Numpy的主要对象是一个均匀的多维数组。Numpy提供了各种函数。可以…

pxe高效网络批量装机

文章目录 一&#xff0c; PXE远程安装服务&#xff08;一&#xff09;三种系统装机的方式&#xff08;二&#xff09;linux装机1. 加载 Boot Loader2. 加载启动安装菜单3. 加载内核和 initrd4. 加载根文件系统5. 运行 Anaconda 安装向导 &#xff08;三&#xff09;实现过程&am…

STM32使用CubeMX创建HAL库工程文件

文章目录 1. STM32CubeMX 2. 界面介绍 3. 使用教程 新建工程 选择芯片界面 ​编辑 配置页面 引脚配置页面 引脚配置界面的颜色指示 配置RCC时钟参数 配置SYS参数 配置时钟树 Project Manager项目管理配置 生成工程文件 KEIL代码编写 1. STM32CubeMX STM32CubeM…