数据结构-栈ArrayDeque的实现
创始人
2024-03-13 20:51:51

 优雅,实在是太优雅了

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

我为何有如此感慨,了解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@Overridepublic 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@Overridepublic E pop() {int h = head;E result = (E) elements[h];if (result == null) {return null;}elements[h] = null;// 00000001// 00000111  最后还是1        1000  0001head = (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 implements Deque {transient Object[] elements;transient private int head;transient private int tail;public ArrayDeque() {// 为了测试,改成长度为2,这样到2就扩容了elements = new Object[2];}// 相当于ArrayDeque的push、addFirst、offerFirst@Overridepublic 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@Overridepublic 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  最后还是2head = (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();}@Overridepublic 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();}
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...