数据结构-栈ArrayDeque的实现

news2024/9/28 7:26:58

 优雅,实在是太优雅了

 能把复杂的东西简单化就是功底。

我为何有如此感慨,了解ArrayDeque的实现你就知道,今天我们要讲的是以栈为思想而实现的ArrayDeque,我们都知道栈是先进后出,和队列相反,如下图,先往数组中依次放入,1,2,3,5,那么在取出时依次取出5,3,2,1

在数组存储时,采取倒放方式,然后定义索引head则可轻易获取当前要出队或要存储的位置,如,默认数组长度为16,先存储1,则会进行计算得到15,head=15,此时数组如下:

[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1]

再存储个2,经过计算head=14,那么此时数组存储如下

[null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,1]

 在取出时则可通过head的值取出数组数据,直接就是先进后出

你可能看出来存储最重要的就是动态的计算出每次小标,如扩容后的呀,删除后的呀。。等等

那么先来准备工作,定义如下全局变量

    // 存储数据对象
    transient Object[] elements;

    // 头部索引
    transient private int head;
    // 尾部索引,双端队列
    transient private int tail;

    public ArrayDeque() {
        elements = new Object[16];
    }

1.入栈

进来看代码你会发现,很简单把,就几行代码,主要进来计算要存储的下标,计算完毕把当前下标赋值给head(留下次使用,取出数据也可使用),并把当前元素赋值到当前下标元素里,如果正常的话我说的这句化是三行代码,人家一行搞定,都不多废一行代码,哈哈

计算公式是通过与运算,&数组长度-1

第一个数据存储时head为0,0-1=-1,-1的二进制为11111111,16-1=15,15的二进制为 00001111

   &   11111111  

        00001111

 =     00001111

根据与运算规则只有两个数都得1才为1,所以-1&15就等于15

第一个数据存储在索引15的位置上,并把head置为15

14       00001110

15       00001111

    =     00001110      =14

第二个数据存储时则为15-1=14,16-1=15,14&15=14

第二个数据存储在索引14的位置上,并把head置为14

以此类推,全部存储完毕head就等于0,此时对比head和tail,tail也等于0,需要扩容

    // 相当于ArrayDeque的push、addFirst、offerFirst
    @Override
    public void push(E e) {
        // 这样就可以从后存入了
        //head=0-1 ,-1的二进制为:11111111
        //16-1 =15 ,15的二进制为:00001111
        //                    &:00001111  计算完还是15,此时head=15
        // 14  14/2=7余0,7/2=3余1,3/2=1余1  1/2=0余1  1110
        // 14的二进制和15的二进制比还是14,利用这样的去计算下标
        elements[head = (head - 1) & (elements.length - 1)] = e;
        System.out.println("push.idx head:" + head);
        // head计算完就为15了,再存储一个就为14,直到容器都存储完毕head和tail相等,需要扩容
        if (head == tail) {
            doubleCapacity();
        }
    }

扩容主要是要阔到原数据的2倍,、关注怎么拷贝数据,怎么处理head和tail的指针索引,

咱们举例假如16长度数组塞满,要扩容进入此方法的情况,请看如下片段对着代码边看边想

p=0;n=16;r=16; newCapacity (32)=16*2

临时开辟32个空间给a数组,第一部分的拷贝,注意:从原数组(elements)第0(p=0)个位置拷贝到a数组中第0个位置,拷贝数量为16(r=16);

第二部分的拷贝:从原数组(elements)第0个位置拷贝到数组a中第16(r=16)个位置,拷贝数量为0(p=0);

看到这里你会想,第二部分的拷贝,没有意义把,

假如第二次扩容时,head=16,tail=16,证明32个数组元素都被占满,此时各参数为:

p=16;n=32;r=16;newcapacity=64

第一部分拷贝为:从原数组(elements)第16(p=16)个位置开始拷贝到a数组中的第0个位置,拷贝数量为16(r=16),这样就实现了后进入的数组放入前半部分

第二部分拷贝为:从原数组(elements)第0个位置开始拷贝到a数组中第16(r=16)位置开始,拷贝数量为16(p=16),这样就实现了先进入放入数组的后半部分

然后把head置为0,这样如果此时取出,就可以取出最后时间插入的数据

tail置为32,这样后面在进行扩容,需要把从32(64长度)之后的位置都占满,此时head就等于32,继续扩容

 private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p;
        int newCapacity = n << 1;
        if (newCapacity < 0) {
            throw new IllegalStateException("Sorry, deque too big");
        }
        Object[] a = new Object[newCapacity];
        // 参数:1.原数组
        // 参数:2.原数组开始位置
        // 参数:3.拷贝的目标数组
        // 参数:4.目标开始位置
        // 参数:5.拷贝数量
        // 先拷贝后半部分放入临时空间的前半部分
        System.arraycopy(elements, p, a, 0, r);
        // 再拷贝前半部分放入临时空间的后半部分,这样就能保证后进先出
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }

2.出栈

出栈也是相当简单,还是那句话,能简单的代码绝对不复杂,前面我们入栈的时候,最新数据的位置已经用head标记过了,所以我们取head得值就可。

我们取出head位置数组值以后,我们要把当前取出得值置为空,然后还得计算把head标记为下一个最新的值,这样再次入栈,出栈,就会在正确的位置

所以取出0的位置数据head=0+1&16-1,最后等于1,这样再次取出时则从1取,取完2&15还是等于2,以此类推。。。

  // 相当于ArrayDeque的pollFirst
    @Override
    public E pop() {
        int h = head;
        E result = (E) elements[h];
        if (result == null) {
            return null;
        }
        elements[h] = null;
        // 00000001
        // 00000111  最后还是1        1000  0001
        head = (h + 1) & (elements.length - 1);
        return result;
    }

全部代码

/**
 * @Author df
 * @Date 2022/12/1 15:15
 * @Version 1.0
 * java中使用stack需要使用ArrayDeque,原stack则很粗糙,java推荐使用Deque栈数据结构
 */
public class ArrayDeque<E> implements Deque<E> {
    transient Object[] elements;

    transient private int head;
    transient private int tail;

    public ArrayDeque() {
        // 为了测试,改成长度为2,这样到2就扩容了
        elements = new Object[2];
    }

    // 相当于ArrayDeque的push、addFirst、offerFirst
    @Override
    public void push(E e) {
        // 这样就可以从后存入了
        //head=0-1 ,-1的二进制为:11111111
        //16-1 =15 ,15的二进制为:00001111
        //                    &:00001111  计算完还是15,此时head=15
        // 14  14/2=7余0,7/2=3余1,3/2=1余1  1/2=0余1  1110
        // 14的二进制和15的二进制比还是14,利用这样的去计算下标
        elements[head = (head - 1) & (elements.length - 1)] = e;
        System.out.println("push.idx head:" + head);
        // head计算完就为15了,再存储一个就为14,直到容器都存储完毕head和tail相等,需要扩容
        if (head == tail) {
            doubleCapacity();
        }
    }

    private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p;
        int newCapacity = n << 1;
        if (newCapacity < 0) {
            throw new IllegalStateException("Sorry, deque too big");
        }
        Object[] a = new Object[newCapacity];
        // 先拷贝后半部分放入临时空间的前半部分
        System.arraycopy(elements, p, a, 0, r);
        // 再拷贝前半部分放入临时空间的后半部分,这样就能保证后进先出
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }

    // 相当于ArrayDeque的pollFirst
    @Override
    public E pop() {
        int h = head;
        E result = (E) elements[h];
        if (result == null) {
            return null;
        }
        elements[h] = null;
        // 00000001
        // 00000111  最后还是1        1000  0001
        // 00000010      2
        // 00000111  最后还是2
        head = (h + 1) & (elements.length - 1);
        return result;
    }

    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }

    @Override
    public boolean isEmpty() {
        return head == tail;
    }

    public static void main(String[] args) {
        ArrayDeque arrayDeque = new ArrayDeque();
        arrayDeque.push(1);
        arrayDeque.push(2);
        arrayDeque.push(3);
        arrayDeque.push(5);
        
        arrayDeque.pop();
    }
}

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

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

相关文章

二叉树的中序遍历三种解法(递归+迭代+线索化)

文章目录递归迭代线索二叉树解法传送门&#xff1a; 添加链接描述 给你一颗二叉树&#xff0c;让你实现中序的遍历 递归 递归没什么好说的&#xff0c;直接无脑递归即可&#xff0c;时间复杂度&#xff1a;O&#xff08;n&#xff09;&#xff0c;空间复杂度&#xff1a;O&am…

搭建环境AI画图stable-diffusion

目录简介环境准备安装conda(方式1)安装conda&#xff08;方式2&#xff0c;推荐&#xff09;验证conda安装成功安装stable-diffusion的环境简介 本文旨在记录过程&#xff0c;偶然看见一个AI画图的&#xff0c;体验看看。 stable-diffusion是一个输入简单图片&#xff0c;输出…

【Java难点攻克】「Guava RateLimiter」针对于限流器的入门到实战和源码原理分析

限流器的思路和算法 如果让你来造一个限流器&#xff0c;有啥想法&#xff1f; 漏桶算法 用一个固定大小的队列。比如设置限流为5qps&#xff0c;1s可以接受5个请求&#xff1b;那我们就造一个大小为5的队列&#xff0c;如果队列为满了&#xff0c;就拒绝请求&#xff1b;如…

JRebelXRebel的配置和使用(进阶篇)

JRebel&XRebel的配置和使用嘚吧嘚设置JRebel快捷键XRebel使用嘚吧嘚 之前简单介绍了JRebel&XRebel的安装和使用&#xff0c;不了解的朋友可以补补课&#x1f606;。 JRebel&XRebel这款插件不仅仅可以用来热部署&#xff0c;所以继续分享一下这款插件的相关使用&a…

12月2日(第四天)

使用myabtis自动生成的时候&#xff0c;发现xml文件只会merge不会覆盖&#xff0c;这时候需要使用插件&#xff1a; <plugin type"org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />MyBatis Generator配置文件context元素的defaultModelType属性…

封装window10-21H1踩的坑,无法分析或处理pass[specialize]应答文件

最近在研究封装镜像&#xff0c;无奈公司不给用win11&#xff0c;只能封装win10 2022年全新Windows11系统封装图文教程&#xff08;一&#xff09;定制母盘 - 小鱼儿yr系统 (yrxitong.com) 坑1&#xff0c;封装好出现无法分析或处理pass[specialize]应答文件 解决办法&#x…

Java基础:String类、static关键字、Arrays类、Math类

第一章 String类 1.1 String类概述 概述 java.lang.String类代表字符串。Java程序中所有的字符串文字&#xff08;例如"abc"&#xff09;都可以被看作是实现此类的实例。 类String中包括用于检查各个字符串的方法&#xff0c;比如用于比较字符串&#xff0c;搜索…

[附源码]计算机毕业设计JAVA新冠疫苗线上预约系统

[附源码]计算机毕业设计JAVA新冠疫苗线上预约系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

Web3.0 DApp(去中心化应用程序)设计架构

先来回顾下 Web2.0 应用程序架构&#xff0c;一图胜千言&#xff1a; 图示是对大多数 Web 2.0 应用程序如何工作的一个很好的抽象总结。以一个博客平台为例&#xff1a; 首先&#xff0c;必须有一个地方来存储基本数据&#xff0c;也就是数据库&#xff1b; 其次&#xff0c;…

快速串联 RNN / LSTM / Attention / transformer / BERT / GPT(未完待续)

参考&#xff1a; 李宏毅2021/2022春机器学习课程王树森 RNN & Transformer 教程 文章目录0. 背景&#xff1a;序列数据及相关任务1. 早期序列模型1.1 循环神经网络 RNN1.2 长短期记忆网络 LSTM1.3 改善 RNN/LSTM 的三个技巧1.3.1 通过堆叠扩展为深度模型1.3.2 使用双向模…

使用学校的服务器跑深度学习

&#x1f31e;欢迎来到深度学习的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f320;本阶段属于练气阶段&#xff0c;希望各位仙友顺利完…

原型工具与代码实现的差距及改进设想

背景 最近经常根据墨刀设计的原型开发微信小程序页面&#xff0c;使用的多了总感觉原型只能参考&#xff0c;原型跟代码实现总差一小步。原型中提供的CSS并不能直接复制到代码中&#xff0c;导致编码人员复刻原型设计时总有差距。本文先列举出一些原型和实现的差距&#xff0c;…

RS232/RS485信号转12路模拟信号 隔离D/A转换器YL34

特点&#xff1a; ● RS-485/232接口&#xff0c;隔离转换成12路标准模拟信号输出 ● 可选型输出4-20mA或0-10V控制其他设备 ● 模拟信号输出精度优于 0.2% ● 可以程控校准模块输出精度 ● 信号输出 / 通讯接口之间隔离耐压3000VDC ● 宽电源供电范围&#xff1a;10 ~ 3…

信息收集的工具简介和常见操作

目录 前言 域名信息 工具 子域名信息 工具 域名DNS解析信息 工具 ip信息 工具 CDN绕过 方法 工具 端口服务信息 常见端口总结 基本概念 扫描工具 指纹识别 识别对象 cms指纹识别 识别思路 工具 其他工具 cdn识别 常用工具 waf识别 触发 工具 Google…

营业利润里首次突破两位数,瑞幸能否延续神话?

近期&#xff0c;瑞幸咖啡公布了Q3财报&#xff0c;三季度继续延续了上半年良好的增长势能。总净收入39亿元&#xff0c;同比增长65.7%&#xff0c;营业利润率首次突破双位数达到了15%。 门店数量增长&#xff1a;Q3新增651家&#xff0c;达到7846家门店。从开店节奏看&#…

RCNN算法思想简单讲解概述————(究极简单的讲述和理解)

学习的过程中发现一个问题&#xff0c;如果不能大概的了解一下一个算法的思想直接去看他的论文&#xff0c;或者去看他算法的讲解就很痛苦&#xff0c;看不懂&#xff0c;学的效率也非常低&#xff0c;类似我之前发的RCNN论文精度的博客。RCNN目标检测算法内容详解&#xff08;…

FreeIPA 统一身份认证实现

1、FreeIPA 简介 FreeIPA是一个用于Linux/Unix环境开源的身份管理系统,提供集中式帐户管理和身份验证,与Windows Active Directory或LDAP的作用类似。FreeIPA集成了389目录服务器、MIT Kerberos、Apache HTTP服务器、NTP、DNS、Dogtag(证书系统)和SSSD,使其成为标识管理、…

使用Flink的各种技术实现WordCount逻辑

使用Flink的各种技术实现WordCount逻辑 在大数据程序中&#xff0c;WordCount程序实现了统计词频的作用&#xff0c;这个WordCount程序也往往在大数据分析处理中一直占着非常重要的地位。统计一天内某网站的访问次数&#xff0c;需要对网站排序后求其词频&#xff0c;统计一段…

【智能电网随机调度】智能电网的双层模型时间尺度随机优化调度(Matlab代码实现)

目录 1 概述 2 数学模型 3 运行结果 4 结论 5 参考文献 6 Matlab代码实现 1 概述 随着可再生能源发电量的增加&#xff0c;配电网的能源管理正成为一项计算上具有挑战性的任务。来自光伏&#xff08;PV&#xff09;装置的太阳能可以在一分钟内发生显着变化。可以命令光伏…

MongoDB安装Mac M1

1、下载安装包&#xff1a; axInstall MongoDB Community Edition on macOS — MongoDB Manualhttps://www.mongodb.com/docs/v6.0/tutorial/install-mongodb-on-os-x/下载解压&#xff0c;重命名为mongodb 放到 /usr/local 目录下 2、配置文件打开配置文件 open -e .bash_p…