数据结构-栈ArrayDeque的实现

 优雅,实在是太优雅了

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

我为何有如此感慨,了解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();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值